commit cf256891811dde7f9ca667a6b6f1279b1385c11d Author: facebook-github-bot Date: Fri Nov 14 14:11:07 2025 -0800 Initial commit fbshipit-source-id: ac9cb65f0bb8a29acff5b6664ede5c4ce7a1a632 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..e2a51d1 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,69 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - main + paths: + - 'Sources/QuickLayout/docs/**' + # Review gh actions docs if you want to further define triggers, paths, etc + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on + +defaults: + run: + shell: bash + working-directory: ./Sources/QuickLayout/docs + +jobs: + build: + name: Build Docusaurus + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: facebook/install-dotslash@latest + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + cache-dependency-path: 'Sources/QuickLayout/docs/package.json' + + - name: Install dependencies + run: yarn install + + - name: Disable watchman + run: | + echo '[buck2]' >> $GITHUB_WORKSPACE/.buckconfig + echo 'file_watcher=notify' >> $GITHUB_WORKSPACE/.buckconfig + - name: Add repo to PATH + run: | + echo "$GITHUB_WORKSPACE" >> $GITHUB_PATH + - name: Build website + run: yarn build + + - name: Upload Build Artifact + uses: actions/upload-pages-artifact@v3 + with: + path: Sources/QuickLayout/docs/build + + deploy: + name: Deploy to GitHub Pages + needs: build + + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e177e12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +.DS_Store + +Podfile.lock +Gemfile.lock + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xcuserstate + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Docs +docs/docsets/ + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control + +Pods/ + +# Add this line if you want to avoid checking in source code from the Xcode workspace +*.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# Bundler +.bundle +vendor + +# Jetbrains +.idea diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3232ed6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,80 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic +address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when there is a +reasonable belief that an individual's behavior may have a negative impact on +the project or its community. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ba48590 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to Meta Open Source Projects + +We want to make contributing to this project as easy and transparent as +possible. + +## Pull Requests +We actively welcome your pull requests. + +Note: pull requests are not imported into the GitHub directory in the usual way. There is an internal Meta repository that is the "source of truth" for the project. The GitHub repository is generated *from* the internal Meta repository. So we don't merge GitHub PRs directly to the GitHub repository -- they must first be imported into internal Meta repository. When Meta employees look at the GitHub PR, there is a special button visible only to them that executes that import. The changes are then automatically reflected from the internal Meta repository back to GitHub. This is why you won't see your PR having being directly merged, but you still see your changes in the repository once it reflects the imported changes. + +1. Fork the repo and create your branch from `main`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Meta's open source projects. + +Complete your CLA here: + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Meta has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## License +By contributing to this project, you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..9048005 --- /dev/null +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -0,0 +1,370 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 0E56B79C2EBCE521004F79FC /* QuickLayoutShowcaseAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E56B79B2EBCE521004F79FC /* QuickLayoutShowcaseAssets.xcassets */; }; + 0E6E97342EB93FDF0030E0DA /* QuickLayout in Frameworks */ = {isa = PBXBuildFile; productRef = 0E6E97332EB93FDF0030E0DA /* QuickLayout */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0E56B79B2EBCE521004F79FC /* QuickLayoutShowcaseAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = QuickLayoutShowcaseAssets.xcassets; path = ../Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets; sourceTree = SOURCE_ROOT; }; + 0E6E96F72EB93F9C0030E0DA /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 0E18AC1A2EC0EA5E00BC420B /* __showcase__ */ = { + isa = PBXFileSystemSynchronizedRootGroup; + name = __showcase__; + path = ../Sources/QuickLayout/QuickLayout/__showcase__; + sourceTree = SOURCE_ROOT; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0E6E96F42EB93F9C0030E0DA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E6E97342EB93FDF0030E0DA /* QuickLayout in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0E6E96EE2EB93F9C0030E0DA = { + isa = PBXGroup; + children = ( + 0E18AC1A2EC0EA5E00BC420B /* __showcase__ */, + 0E56B79B2EBCE521004F79FC /* QuickLayoutShowcaseAssets.xcassets */, + 0E6E97322EB93FDF0030E0DA /* Frameworks */, + 0E6E96F82EB93F9C0030E0DA /* Products */, + ); + sourceTree = ""; + }; + 0E6E96F82EB93F9C0030E0DA /* Products */ = { + isa = PBXGroup; + children = ( + 0E6E96F72EB93F9C0030E0DA /* Demo.app */, + ); + name = Products; + sourceTree = ""; + }; + 0E6E97322EB93FDF0030E0DA /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0E6E96F62EB93F9C0030E0DA /* Demo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0E6E97022EB93F9D0030E0DA /* Build configuration list for PBXNativeTarget "Demo" */; + buildPhases = ( + 0E6E96F32EB93F9C0030E0DA /* Sources */, + 0E6E96F42EB93F9C0030E0DA /* Frameworks */, + 0E6E96F52EB93F9C0030E0DA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 0E18AC1A2EC0EA5E00BC420B /* __showcase__ */, + ); + name = Demo; + packageProductDependencies = ( + 0E6E97332EB93FDF0030E0DA /* QuickLayout */, + ); + productName = SampleApp; + productReference = 0E6E96F72EB93F9C0030E0DA /* Demo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0E6E96EF2EB93F9C0030E0DA /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2600; + LastUpgradeCheck = 2600; + TargetAttributes = { + 0E6E96F62EB93F9C0030E0DA = { + CreatedOnToolsVersion = 26.0; + }; + }; + }; + buildConfigurationList = 0E6E96F22EB93F9C0030E0DA /* Build configuration list for PBXProject "Demo" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 0E6E96EE2EB93F9C0030E0DA; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 0E6E97312EB93FCE0030E0DA /* XCLocalSwiftPackageReference "../" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 0E6E96F82EB93F9C0030E0DA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0E6E96F62EB93F9C0030E0DA /* Demo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0E6E96F52EB93F9C0030E0DA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E56B79C2EBCE521004F79FC /* QuickLayoutShowcaseAssets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0E6E96F32EB93F9C0030E0DA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 0E6E97002EB93F9D0030E0DA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 0E6E97012EB93F9D0030E0DA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 0E6E97032EB93F9D0030E0DA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = ""; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.meta.SampleApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0E6E97042EB93F9D0030E0DA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = ""; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.meta.SampleApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0E6E96F22EB93F9C0030E0DA /* Build configuration list for PBXProject "Demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E6E97002EB93F9D0030E0DA /* Debug */, + 0E6E97012EB93F9D0030E0DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0E6E97022EB93F9D0030E0DA /* Build configuration list for PBXNativeTarget "Demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E6E97032EB93F9D0030E0DA /* Debug */, + 0E6E97042EB93F9D0030E0DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 0E6E97312EB93FCE0030E0DA /* XCLocalSwiftPackageReference "../" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 0E6E97332EB93FDF0030E0DA /* QuickLayout */ = { + isa = XCSwiftPackageProductDependency; + package = 0E6E97312EB93FCE0030E0DA /* XCLocalSwiftPackageReference "../" */; + productName = QuickLayout; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 0E6E96EF2EB93F9C0030E0DA /* Project object */; +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b93be90 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..d240d97 --- /dev/null +++ b/Package.swift @@ -0,0 +1,81 @@ +// swift-tools-version: 6.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import CompilerPluginSupport +import PackageDescription + +let package = Package( + name: "QuickLayout", + platforms: [ + .iOS(.v15), + .macOS(.v10_15), + ], + products: [ + .library( + name: "QuickLayout", + targets: ["QuickLayout"] + ), + .library( + name: "QuickLayoutCore", + targets: ["QuickLayoutCore"] + ), + .library( + name: "FastResultBuilder", + targets: ["FastResultBuilder"] + ), + .library( + name: "QuickLayoutBridge", + targets: ["QuickLayoutBridge"] + ), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "602.0.0") + ], + targets: [ + .target( + name: "QuickLayout", + dependencies: [ + "QuickLayoutMacro", + "QuickLayoutBridge", + ], + path: "Sources/QuickLayout/QuickLayout", + exclude: [ + "__showcase__/" + ] + ), + .macro( + name: "QuickLayoutMacro", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ], + path: "Sources/QuickLayout/QuickLayoutMacro", + ), + .target( + name: "QuickLayoutCore", + path: "Sources/QuickLayout/QuickLayoutCore", + ), + .target( + name: "FastResultBuilder", + path: "Sources/FastResultBuilder/FastResultBuilder", + exclude: [ + "__tests__/" + ] + ), + .target( + name: "QuickLayoutBridge", + dependencies: ["FastResultBuilder", "QuickLayoutCore"], + path: "Sources/QuickLayout/QuickLayoutBridge", + exclude: [ + "__server_snapshot_tests__", + "__tests__", + ] + ), + ], +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..abdca50 --- /dev/null +++ b/README.md @@ -0,0 +1,240 @@ +# QuickLayout + +QuickLayout is a declarative layout library for iOS, designed to work seamlessly with UIKit. It is lightning-fast, incredibly simple to use, and offers a powerful set of modern layout primitives. + +QuickLayout's API will feel natural to many iOS engineers. With a small and well tested codebase, it is production ready. QuickLayout has significant usage across many of Instagram's core features. + +### Code Sample + +```swift +import QuickLayout + +@QuickLayout +class MyCellView: UIView { + + let titleLabel = UILabel() + let subtitleLabel = UILabel() + + var body: Layout { + HStack { + VStack(alignment: leading) { + titleLabel + Spacer(4) + subtitleLabel + } + Spacer() + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + } +} +``` + +Above is a fully functional view. Subviews are managed automatically, layout is performed at the correct time, and sizeThatFits returns an accurate value. + +### Features + +- ⭐️ **Lightning-Fast**: Custom layout engine for blazing fast performance—no Auto Layout or Flexbox overhead. +- 🧩 **Modern Layout Primitives**: Includes `HStack`, `VStack`, `ZStack`, `Grid`, and `Flow` as lightweight, pure Swift structs that don’t add extra views. +- 🚀 **Easy Integration**: Simple @QuickLayout macro for fast setup and automatic subview management. +- 🧵 **Thread-Safe**: [With some caveats](https://facebookincubator.github.io/QuickLayout/concepts/thread-safety/), our API can be used concurrently and off the main thread. + +### Superior Performance over Auto Layout + +QuickLayout achieves lightning-fast performance by using a custom-built layout engine that completely avoids Auto Layout and Flexbox, eliminating the overhead of constraint solving and the risk of constraint errors. Instead, QuickLayout provides a declarative API with lightweight layout primitives implemented as pure Swift structs that do not add extra views to the view hierarchy. This results in lower memory usage and faster layout calculations. + +In benchmarks, QuickLayout is up to 3× faster and 4× more memory efficient than UIStackViews built with Auto Layout. By eliminating constraint solving and extra container views, QuickLayout delivers consistently high performance and a smaller memory footprint, making it ideal for demanding app surfaces. + +For a comprehensive overview of all features, usage examples, and best practices, check out the [**QuickLayout Handbook**](https://facebookincubator.github.io/QuickLayout/). + +## Quickstart + +QuickLayout is a declarative layout library built to be used with regular UIViews. It can coexist with manual layout methods, allowing for gradual adoption. + +### Stacks + +At the heart of QuickLayout are Stacks: +- VStack layouts child elements vertically, +- HStack positions child elements horizontally. + +For example, to make the following layout (image below), you can use VStack with leading alignment and spacing of four points: + + + +```swift +let label1 = UILabel(text: "Kylee Lessie") +let label2 = UILabel(text: "Developer") + +var body: Layout { + VStack(alignment: .leading, spacing: 4) { + label1 + label2 + } +} +``` + +You can nest Stacks as many times as you wish. For instance, you can add an icon to the left of two labels by using HStack: + + + +```swift +let avatarView = UIImage(image: avatarIcon) +let label1 = UILabel(text: "Kylee Lessie") +let label2 = UILabel(text: "Developer") + +var body: Layout { + HStack(spacing: 8) { + avatarView + VStack(alignment: .leading, spacing: 4) { + label1 + label2 + } + } +} +``` + +### Alignment in Stacks + +The alignment property in stacks does not position the stack within its parent; instead, it controls the alignment of the child elements inside the stack. + +The alignment property of a VStack only applies to the horizontal alignment of the contained views. Similarly, the alignment property for an HStack only controls the vertical alignment. + + + +### Flexible Spacer + +A flexible Spacer is an adaptive element that expands as much as possible. For example, when placed within an HStack, a spacer expands horizontally as much as the stack allows, moving sibling views out of the way within the stack’s size limits. + + + +```swift +var body: Layout { + HStack { + view1 + Spacer() + } +} +``` + + + +```swift +var body: Layout { + HStack { + view1 + Spacer() + view2 + } +} +``` + + + +```swift +var body: Layout { + HStack { + Spacer() + view1 + } +} +``` + +### Fixed spacer and Spacing + + + +A fixed space between views can be specified with a Spacer as in the snippet below: + + +```swift +var body: Layout { + HStack { + view1 + Spacer(8) + view2 + Spacer(8) + view3 + } +} +``` +You can achieve the same fixed spacing between views using the Stack's spacing parameter: + +```swift +var body: Layout { + HStack(spacing: 8) { + view1 + view2 + view3 + } +} +``` + +### Padding + +A padding can be added to any view or layout element: + +```swift +var body: Layout { + HStack(spacing: 8) { + view1 + view2 + view3 + } + .padding(.horizontal, 16) +} +``` + +### Frame + +The frame does not directly change the size of the target view, but it acts like a "picture frame" by suggesting the child its size and positioning it inside its bounds. For example, if the UIImageView in the following layout has an icon that is 10x10 points in size, the icon will be surrounded by 45 points of empty space on each side. However, if the icon has a size of 90x90 points, it will be surrounded only by 5 points of empty space on each side. + +```swift +var body: Layout { + imageView + .frame(width: 100, height: 100) +} +``` + +To make UIImageView acquire the size of the frame, it needs to be wrapped into resizable modifier: + +```swift +var body: Layout { + imageView + .resizable() + .frame(width: 100, height: 100) +} +``` + +## Learn More +For full documentation, visit the [**QuickLayout Handbook**](https://facebookincubator.github.io/QuickLayout/). + +## Installation +Use Swift Package Manager. Link and import QuickLayout target. + +## Credits + +- [Constantine Fry](https://github.com/constantine-fry) — Concept, architecture, layout and API design +- [Jordan Smith](https://github.com/jordansmithnz) — @QuickLayout macro and API design, body API and automatic subview management +- [Fabio Milano](https://github.com/fabiomassimo) — Demo showcase, BUCK support +- [Jordan Smith](https://github.com/jordansmithnz) & [Fabio Milano](https://github.com/fabiomassimo) — Instagram adoption +- [Andrey Mishanin](https://github.com/Andrey-Mishanin) & [Constantine Fry](https://github.com/constantine-fry) — Stack Layout +- [Constantine Fry](https://github.com/constantine-fry) & [Saadh Zahid](https://github.com/saadhzahid) — Flow and Grid Layout +- [Jordan Smith](https://github.com/jordansmithnz) — Custom alignment and alignment guides +- [Thong Nguyen](https://github.com/tumtumtum) — Fast Result Builder + +## Special Thanks + +To **Scott James Remnant** of [netsplit.com](https://netsplit.com/swiftui/) and **Javier** of [SwiftUI Labs](https://swiftui-lab.com/) for their research and articles on SwiftUI. + +To **Chris Eidhof**, **Daniel Eggert**, and **Florian Kugler** of [objc.io](https://www.objc.io) for their continued work on exploring SwiftUI layout. + +To **Luc Dion** for his work on [LayoutFrameworkBenchmark](https://github.com/layoutBox/LayoutFrameworkBenchmark). + +To the early adopters - **Sash Zats**, **Cory Wilhite**, **Chaoshuai Lyu**, **Steven Liu**, **Erik Kunz**, **Min Kim**, and many others - for their feedback and early adoption efforts. + +## License +QuickLayout is MIT-licensed, as found in the LICENSE file. + +## Legal + +Copyright © Meta Platforms, Inc • Terms of UsePrivacy Policy diff --git a/Sources/FastResultBuilder/FastResultBuilder/FastArrayBuilder.swift b/Sources/FastResultBuilder/FastResultBuilder/FastArrayBuilder.swift new file mode 100644 index 0000000..927f0f1 --- /dev/null +++ b/Sources/FastResultBuilder/FastResultBuilder/FastArrayBuilder.swift @@ -0,0 +1,58 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +/* + * A resultBuilder that builds an array of the given generic type (the results). + */ +@resultBuilder +public struct FastArrayBuilder { + public static func buildBlock(_ expressions: any FastExpression...) -> BlockExpression { + BlockExpression(expressions: expressions) + } + + public static func buildBlock(_ expressions: [any FastExpression]) -> BlockExpression { + BlockExpression(expressions: expressions) + } + + public static func buildExpression(_ value: T) -> ValueExpression { + ValueExpression(value: value) + } + + public static func buildExpression(_ value: T?) -> any FastExpression { + if let value { + ValueExpression(value: value) + } else { + NothingExpression() + } + } + + public static func buildExpression(_ expression: FastExpression) -> any FastExpression { + expression + } + + public static func buildOptional(_ block: BlockExpression?) -> any FastExpression { + block ?? NothingExpression() + } + + public static func buildEither(first block: BlockExpression) -> BlockExpression { + block + } + + public static func buildEither(second block: BlockExpression) -> BlockExpression { + block + } + + public static func buildArray(_ elements: [FastExpression]) -> ArrayExpression { + ArrayExpression(elements: elements) + } + + public static func buildFinalResult(_ block: BlockExpression) -> [T] { + ResultsExpressionVisitor.getResults(block: block) + } +} diff --git a/Sources/FastResultBuilder/FastResultBuilder/FastExpressions.swift b/Sources/FastResultBuilder/FastResultBuilder/FastExpressions.swift new file mode 100644 index 0000000..9e74426 --- /dev/null +++ b/Sources/FastResultBuilder/FastResultBuilder/FastExpressions.swift @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +public protocol FastExpression { + func apply(visitor: inout some ExpressionVisitor) +} + +public struct ValueExpression: FastExpression { + public let value: ValueType + + public init(value: ValueType) { + self.value = value + } + + public func apply(visitor: inout some ExpressionVisitor) { + visitor.visit(value: self) + } +} + +public struct NothingExpression: FastExpression { + public init() {} + + public func apply(visitor: inout some ExpressionVisitor) { + visitor.visit(nothing: self) + } +} + +public struct BlockExpression: FastExpression { + let expressions: [FastExpression] + + public init(expressions: [FastExpression]) { + self.expressions = expressions + } + + public func apply(visitor: inout some ExpressionVisitor) { + visitor.visit(block: self) + } +} + +public struct ArrayExpression: FastExpression { + let elements: [FastExpression] + + public init(elements: [FastExpression]) { + self.elements = elements + } + + public func apply(visitor: inout some ExpressionVisitor) { + visitor.visit(array: self) + } +} diff --git a/Sources/FastResultBuilder/FastResultBuilder/ResultsExpressionVisitor.swift b/Sources/FastResultBuilder/FastResultBuilder/ResultsExpressionVisitor.swift new file mode 100644 index 0000000..855fdfe --- /dev/null +++ b/Sources/FastResultBuilder/FastResultBuilder/ResultsExpressionVisitor.swift @@ -0,0 +1,54 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +public protocol ExpressionVisitor { + mutating func visit(value: ValueExpression) + mutating func visit(nothing: NothingExpression) + mutating func visit(block: BlockExpression) + mutating func visit(array: ArrayExpression) +} + +public struct ResultsExpressionVisitor: ExpressionVisitor { + var results: [T] = [] + + public mutating func visit(value: ValueExpression) { + // Only interested in values that we can add to the results + if let value = value.value as? T { + results.append(value) + } + } + + public mutating func visit(array: ArrayExpression) { + for expression in array.elements { + visit(any: expression) + } + } + + public mutating func visit(nothing: NothingExpression) { + } + + public mutating func visit(any expression: FastExpression) { + expression.apply(visitor: &self) + } + + public mutating func visit(block: BlockExpression) { + for expression in block.expressions { + visit(any: expression) + } + } + + static func getResults(block: BlockExpression) -> [T] { + var visitor = ResultsExpressionVisitor() + + visitor.results.reserveCapacity(block.expressions.count) + visitor.visit(block: block) + + return visitor.results + } +} diff --git a/Sources/FastResultBuilder/FastResultBuilder/__tests__/FastResultsBuilderTests.swift b/Sources/FastResultBuilder/FastResultBuilder/__tests__/FastResultsBuilderTests.swift new file mode 100644 index 0000000..ad1a130 --- /dev/null +++ b/Sources/FastResultBuilder/FastResultBuilder/__tests__/FastResultsBuilderTests.swift @@ -0,0 +1,90 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FastResultBuilder +import Foundation +import XCTest + +@resultBuilder +public struct StackArrayBuilder { + public static func buildBlock() -> [T] { [] } + public static func buildExpression(_ expression: T) -> [T] { [expression] } + public static func buildExpression(_ expression: T?) -> [T] { [expression].compactMap { $0 } } + public static func buildExpression(_ expression: [T]) -> [T] { expression } + public static func buildBlock(_ components: [T]...) -> [T] { components.flatMap { $0 } } + public static func buildArray(_ components: [[T]]) -> [T] { components.flatMap { $0 } } + public static func buildOptional(_ component: [T]?) -> [T] { component ?? [] } + public static func buildEither(first component: [T]) -> [T] { component } + public static func buildEither(second component: [T]) -> [T] { component } + public static func buildLimitedAvailability(_ component: [T]) -> [T] { component } +} + +struct Foo { + @FastArrayBuilder var values: [Int] { + 1 + 2 + 10 as Int? + + for i in 1...10 { + i + } + + if true { + 99 + } + + if false { + 0 + } else { + 1 + } + } +} + +struct Bar { + @StackArrayBuilder var values: [Int] { + 1 + 2 + 10 as Int? + + for i in 1...10 { + i + } + + if true { + 99 + } + + if false { + 0 + } else { + 1 + } + } +} + +class FastResultsBuilderTests: XCTestCase { + func test1() { + XCTAssertEqual(Foo().values, [1, 2, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 99, 1]) + } + + func ignore_testPerf1() { + self.measure { + for _ in 1...100000 { + XCTAssertEqual(Foo().values, [1, 2, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 99, 1]) + } + } + } + + func ignore_testPerf2() { + self.measure { + for _ in 1...100000 { + XCTAssertEqual(Bar().values, [1, 2, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 99, 1]) + } + } + } +} diff --git a/Sources/QuickLayout/QuickLayout/QuickLayout.swift b/Sources/QuickLayout/QuickLayout/QuickLayout.swift new file mode 100644 index 0000000..c9d63d9 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/QuickLayout.swift @@ -0,0 +1,8 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@_exported import QuickLayoutBridge diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/1-HelloWorld/HelloWorldView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/1-HelloWorld/HelloWorldView.swift new file mode 100644 index 0000000..325cda2 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/1-HelloWorld/HelloWorldView.swift @@ -0,0 +1,52 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +@QuickLayout +final class HelloWorldView: UIView { + + private let imageView = { + let imageView = UIImageView() + imageView.image = UIImage(systemName: "globe.americas") + return imageView + }() + + private let titleLabel = { + let label = UILabel() + label.text = "Hello World!" + label.textColor = .label + return label + }() + + private let subtitleLabel = { + let label = UILabel() + label.text = "This is a simple layout with QuickLayout" + label.textColor = .secondaryLabel + return label + }() + + var body: Layout { + HStack { + imageView + Spacer(8) + VStack(alignment: .leading) { + titleLabel + subtitleLabel + } + Spacer() + } + .padding(16) + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + HelloWorldView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/2-StateManagementView/StateManagementView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/2-StateManagementView/StateManagementView.swift new file mode 100644 index 0000000..c26fb93 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/2-StateManagementView/StateManagementView.swift @@ -0,0 +1,61 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +@MainActor +@QuickLayout +final class StateManagementView: UIView { + + private var count = 0 + private let label = UILabel() + + private lazy var button = { + let button = UIButton(type: .system) + button.setTitle("Add One", for: .normal) + button.addTarget(self, action: #selector(addCount), for: .touchUpInside) + return button + }() + + private lazy var reset = { + let button = UIButton(type: .system) + button.setTitle("Reset", for: .normal) + button.tintColor = .systemRed + button.addTarget(self, action: #selector(resetCount), for: .touchUpInside) + return button + }() + + var body: Layout { + VStack(spacing: 8) { + if count > 0 { + label + } + HStack(spacing: 8) { + button + reset + } + } + } + + @objc private func addCount() { + count += 1 + label.text = "Count: \(count)" + setNeedsLayout() + } + + @objc private func resetCount() { + count = 0 + setNeedsLayout() + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + StateManagementView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/3-Animations/UpdatingWithAnimationView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/3-Animations/UpdatingWithAnimationView.swift new file mode 100644 index 0000000..eb80f4b --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/3-Animations/UpdatingWithAnimationView.swift @@ -0,0 +1,67 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +private let numberColors: [UIColor] = [.systemRed, .systemBlue, .systemGray, .systemPink, .systemTeal, .systemBrown, .systemOrange, .systemYellow, .systemGreen, .systemIndigo] + +@MainActor +@QuickLayout +final class UpdatingWithAnimationView: UIView { + + private let numberViews: [UIView] = (1...10).map { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .title1) + label.text = "\($0)" + label.textColor = .white + label.layer.cornerRadius = 8 + label.layer.cornerCurve = .continuous + label.layer.masksToBounds = true + label.textAlignment = .center + label.backgroundColor = numberColors[$0 - 1] + return label + } + + private lazy var button: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Update with Animations", for: .normal) + button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + return button + }() + + var numberViewIsActive = [Bool].init(repeating: false, count: 10) + + var body: Layout { + VStack { + HStack(spacing: 8) { + for (index, numberView) in numberViews.enumerated() { + if numberViewIsActive[index] { + numberView + .expand(by: CGSize(width: 16, height: 8)) + } + } + } + Spacer(20) + button + } + } + + @objc private func buttonTapped() { + numberViewIsActive = numberViewIsActive.map { _ in Bool.random() } + setNeedsLayout() + UIView.animate(withDuration: 0.5, delay: 0, options: [.beginFromCurrentState, .curveEaseInOut]) { + self.layoutIfNeeded() + } + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + UpdatingWithAnimationView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsCardViewCell.swift b/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsCardViewCell.swift new file mode 100644 index 0000000..b1eb15c --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsCardViewCell.swift @@ -0,0 +1,142 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +/// Declarative Layout in a UICollectionView based screen. +/// **Key concepts**: +/// - Animations +/// - Dynamic Text support +/// - RTL support for free (edit scheme: https://pxl.cl/5MGDW) +@MainActor +protocol BarCardViewCellDelegate: AnyObject { + func didTapShareButton(_ cell: BarCardViewCell, bar: BarModel) +} + +@QuickLayout +final class BarCardViewCell: UICollectionViewCell { + static let reuseIdentifier = "BarCardViewCellReuseIdentifier" + + private var expandDescription = false + + private(set) var bar: BarModel? + + weak var delegate: BarCardViewCellDelegate? + + private let blurEffectView = { + let blurEffect = UIBlurEffect(style: .light) + let visualEffectView = UIVisualEffectView(effect: blurEffect) + visualEffectView.layer.masksToBounds = true + visualEffectView.layer.cornerRadius = 20 + return visualEffectView + }() + + private let name = { + let label = UILabel() + let boldTitle1Font = UIFont(descriptor: UIFont.preferredFont(forTextStyle: .title1).fontDescriptor.withSymbolicTraits(.traitBold) ?? UIFont.preferredFont(forTextStyle: .title1).fontDescriptor, size: 0) + label.font = boldTitle1Font + label.textColor = .white + label.adjustsFontForContentSizeCategory = true + return label + }() + + private let locationName = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .body) + label.textColor = .white + label.adjustsFontForContentSizeCategory = true + return label + }() + + private lazy var descriptionLabel = { + let label = UILabel() + label.adjustsFontForContentSizeCategory = true + label.numberOfLines = 0 + label.textColor = .white + label.font = UIFont.preferredFont(forTextStyle: .callout) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapContentLabel)) + addGestureRecognizer(tapGesture) + return label + }() + + private lazy var shareButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal) + button.tintColor = .white + button.addTarget(self, action: #selector(didTapShareButton), for: .touchUpInside) + return button + }() + + private let mediaContent = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + return imageView + }() + + override func prepareForReuse() { + super.prepareForReuse() + /// Reset the internal state + expandDescription = false + } + + // MARK: - Layout + + var body: Layout { + ZStack(alignment: .bottom) { + mediaContent.resizable() + HStack(alignment: .top) { + VStack(alignment: .leading, spacing: 5) { + name + locationName + Spacer(10) + descriptionLabel + .frame(maxHeight: expandDescription ? nil : 50) + } + Spacer() + shareButton + } + .padding(15) + .background { blurEffectView } + .offset(y: -(window?.safeAreaInsets.bottom ?? 0)) + } + .padding(.horizontal, 15) + } + + // MARK: - Setup + + func prepare(with item: BarModel) { + bar = item + mediaContent.image = item.coverImage + name.text = item.name + locationName.text = item.locationName + descriptionLabel.text = item.description + } + + // MARK: - Actions + + @objc func didTapContentLabel() { + expandDescription.toggle() + setNeedsLayout() + descriptionLabel.setNeedsDisplay() + UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.4, options: []) { + self.shareButton.alpha = self.expandDescription ? 1.0 : 0.0 + self.layoutIfNeeded() + } + } + + @objc func didTapShareButton() { + guard let bar else { return } + delegate?.didTapShareButton(self, bar: bar) + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + BarsListViewController() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsListViewController.swift b/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsListViewController.swift new file mode 100644 index 0000000..8c94f63 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsListViewController.swift @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import UIKit + +final class BarsListViewController: UIViewController { + private var dataSource: UICollectionViewDiffableDataSource? + + private lazy var collectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 0 + layout.minimumLineSpacing = 0 + // patternlint-disable-next-line ig-avoid-uiscreen-bounds-swift + layout.itemSize = UIScreen.main.bounds.size + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.register(BarCardViewCell.self, forCellWithReuseIdentifier: BarCardViewCell.reuseIdentifier) + + collectionView.alwaysBounceVertical = true + collectionView.isPagingEnabled = true + collectionView.contentInsetAdjustmentBehavior = .never + collectionView.backgroundColor = .black + + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + configureDataSource() + } + + override func loadView() { + self.view = collectionView + } + + func configureDataSource() { + dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView: UICollectionView, indexPath: IndexPath, item: BarModel) -> UICollectionViewCell? in + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BarCardViewCell.reuseIdentifier, for: indexPath) as? BarCardViewCell else { + fatalError("Cannot create new cell") + } + cell.prepare(with: item) + cell.delegate = self + + return cell + } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([1]) + snapshot.appendItems(BarsStore.entries) + dataSource?.apply(snapshot, animatingDifferences: false) + } +} + +extension BarsListViewController: BarCardViewCellDelegate { + func didTapShareButton(_ cell: BarCardViewCell, bar: BarModel) { + let itemsToShare: [Any] = ["Check this out!", bar.shareURL] + let activityVC = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil) + activityVC.excludedActivityTypes = [.message, .airDrop] + present(activityVC, animated: true, completion: nil) + } +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsModels.swift b/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsModels.swift new file mode 100644 index 0000000..515f8f4 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/4-ListExample/BarsModels.swift @@ -0,0 +1,29 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import UIKit + +struct BarModel { + let coverImage: UIImage + let name: String + let locationName: String + let description: String + let shareURL: URL +} +nonisolated extension BarModel: Hashable {} + +// swiftlint:disable force_unwrapping +actor BarsStore { + static let entries = { + return [ + BarModel(coverImage: UIImage(named: "1_handshake")!, name: "Handshake", locationName: "🇲🇽 Mexico City", description: "Hidden behind an enigmatic black door in Colonia Juarez, whose only sign is the number '13', lies Handshake Speakeasy. Copper arches frame the backbar and the long, marble counter sits in front like an altar to the cocktail. The devil is in the detail here: in the branded silver fixtures, the seriously smart mini-cocktail serves and the cooling system under the bar that ensures every glass stays frosty.", shareURL: URL(string: "https://www.instagram.com/handshake_bar")!), + BarModel(coverImage: UIImage(named: "2_superbueno")!, name: "Superbueno", locationName: "🇺🇸 New York City", description: "Superbueno is a perfectly named bar. The bilingual explanation perfectly encapsulates the zeitgeist of this vibrant ode to Mexican-American culture. Industry veteran Ignacio ‘Nacho’ Jimenez collaborated with power publican Greg Boehm (Katana Kitten, Mace) to open an elevated cantina on New York City’s Lower East Side. Together, the power duo has created a festive environment buzzing with a diverse crowd from early afternoon opening through to late-night close. ", shareURL: URL(string: "https://www.instagram.com/superbuenonyc/")!), + BarModel(coverImage: UIImage(named: "3_overstory")!, name: "Overstory", locationName: "🇺🇸 New York City", description: "A spectacular view of the New York City skyline serves as backdrop for Overstory, where the cocktail magic served up is just as memorable as the vistas beyond. Located on the 64th floor of historic Financial District building 70 Pine Street, the experience begins in the grand lobby of the art deco building, where guests are welcomed and escorted via elevator to the petite jewel box of a bar. There, an expansive wraparound terrace offers guests the chance to soak in the glittering energy of the city before (or after) settling in for cocktail service.", shareURL: URL(string: "https://www.instagram.com/overstory")!), + BarModel(coverImage: UIImage(named: "4_martiny")!, name: "Martiny's", locationName: "🇺🇸 New York City", description: "Takuma Watanabe earned a devout following helming the bar program at New York City’s legendary Angel’s Share. After the beloved speakeasy closed, Watanabe opened Martiny’s, an homage to the drinking cultures of his Tokyo birthplace as well as his Manhattan home. Occupying a carriage house in Gramercy, once owned by artist Philip Martiny, the three-storey lounge is a temple to luxury.", shareURL: URL(string: "https://www.instagram.com/martinys_nyc")!), + ] + }() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/AlignmentGuidesView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/AlignmentGuidesView.swift new file mode 100644 index 0000000..b8f0e34 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/AlignmentGuidesView.swift @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +@QuickLayout +final class AlignmentGuidesView: UIView { + + private let groceriesTitleLabel = { + let label = UILabel() + label.text = "Groceries" + label.font = UIFont.systemFont(ofSize: 18, weight: .bold) + return label + }() + + private let groceryItemLabels = ["Milk", "Eggs", "Bananas"].map { + let label = UILabel() + label.text = $0 + return label + } + + private let tasksTitleLabel = { + let label = UILabel() + label.text = "Tasks" + label.font = UIFont.systemFont(ofSize: 18, weight: .bold) + return label + }() + + private let taskItemLabels = ["Laundry", "Cook dinner"].map { + let label = UILabel() + label.text = $0 + return label + } + + var body: Layout { + /// + ///Note: This is for demonstration purposes only. Prefer using .padding() modifier when the alignmentGuide returns fixed value. + /// + VStack(alignment: .leading, spacing: 5) { + groceriesTitleLabel + for item in groceryItemLabels { + item.alignmentGuide(.leading) { _ in -10 } + } + Spacer(20) + tasksTitleLabel + for item in taskItemLabels { + item.alignmentGuide(.leading) { _ in -10 } + } + } + } +} + +@available(iOS 17, *) +#Preview { + AlignmentGuidesView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/CustomAlignmentView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/CustomAlignmentView.swift new file mode 100644 index 0000000..fcbdd5f --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/CustomAlignmentView.swift @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +private extension VerticalAlignment { + + struct FirstThirdAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height / 3 + } + } + + static let firstThirdAlignment = VerticalAlignment(FirstThirdAlignment.self) +} + +@QuickLayout +final class CustomAlignmentView: UIView { + + private let colorViews = (0...8).map { index in + let view = UIView() + view.backgroundColor = .systemBlue + return view + } + + private let labels = (0...8).map { index in + let label = UILabel() + label.text = "\(index)" + label.textColor = .white + label.font = .monospacedDigitSystemFont(ofSize: 18, weight: .medium) + return label + } + + var body: Layout { + HStack(alignment: .firstThirdAlignment, spacing: 2) { + VStack(spacing: 2) { + colorViews[0].overlay { labels[0] } + colorViews[1].overlay { labels[1] } + colorViews[2].overlay { labels[2] } + }.frame(height: 140) + VStack(spacing: 2) { + colorViews[3].overlay { labels[3] } + colorViews[4].overlay { labels[4] } + colorViews[5].overlay { labels[5] } + }.frame(height: 250) + VStack(spacing: 2) { + colorViews[6].overlay { labels[6] } + colorViews[7].overlay { labels[7] } + colorViews[8].overlay { labels[8] } + }.frame(height: 180) + } + .padding(20) + } +} + +// MARK: - Preview + +@available(iOS 17, *) +#Preview { + CustomAlignmentView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/EmojiAlignmentView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/EmojiAlignmentView.swift new file mode 100644 index 0000000..550871d --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/EmojiAlignmentView.swift @@ -0,0 +1,95 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +private extension VerticalAlignment { + + private struct EmojiTitleAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[VerticalAlignment.bottom] + } + } + + static let emojiTitle = VerticalAlignment( + EmojiTitleAlignment.self + ) +} + +@QuickLayout +final class EmojiAlignmentView: UIView { + + private let emojis: [EmojiDisplay] + private let titleView = UILabel() + + init() { + self.titleView.text = "Odd one out?" + self.titleView.font = .systemFont(ofSize: 17, weight: .bold) + self.emojis = _models.map { + let emojiView = UILabel() + emojiView.text = $0.emojiString + let titleView = UILabel() + titleView.text = $0.emojiTitle + let descriptionView: UILabel? + if let description = $0.emojiDescription { + descriptionView = UILabel() + descriptionView?.text = description + descriptionView?.textColor = .secondaryLabel + descriptionView?.font = .systemFont(ofSize: 13) + emojiView.font = .systemFont(ofSize: 40) + } else { + descriptionView = nil + } + return EmojiDisplay(emojiView: emojiView, emojiTitleView: titleView, emojiDescriptionView: descriptionView) + } + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var body: Layout { + VStack(spacing: 30) { + titleView + HStack(alignment: .emojiTitle, spacing: 16) { + for emojiDisplay in emojis { + VStack { + emojiDisplay.emojiView + emojiDisplay.emojiTitleView + .alignmentGuide(.emojiTitle) { _ in 0 } + emojiDisplay.emojiDescriptionView + } + } + } + } + } +} + +private struct Model { + let emojiString: String + let emojiTitle: String + let emojiDescription: String? +} + +private struct EmojiDisplay { + let emojiView: UIView + let emojiTitleView: UIView + let emojiDescriptionView: UIView? +} + +private let _models: [Model] = [ + .init(emojiString: "🍔", emojiTitle: "Burger", emojiDescription: nil), + .init(emojiString: "🍇", emojiTitle: "Grape", emojiDescription: nil), + .init(emojiString: "🏡", emojiTitle: "House", emojiDescription: "This one!"), + .init(emojiString: "🍎", emojiTitle: "Apple", emojiDescription: nil), +] + +@available(iOS 17, *) +#Preview { + EmojiAlignmentView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/FirstLabelAlignmentView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/FirstLabelAlignmentView.swift new file mode 100644 index 0000000..f6244b6 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/FirstLabelAlignmentView.swift @@ -0,0 +1,68 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +private extension VerticalAlignment { + + private struct TitleCenterAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[VerticalAlignment.center] + } + } + + static let titleCenterAlignment = VerticalAlignment(TitleCenterAlignment.self) +} + +@QuickLayout +final class FirstLabelAlignmentView: UIView { + + let iconView = UIImageView(image: UIImage(systemName: "face.smiling")!) // swiftlint:disable:this force_unwrapping + let titleView = UILabel() + let subtitleLabel = UILabel() + + init() { + self.titleView.text = "Mauris fringilla ligula felis, nec pharetra velit congue id. Aenean hendrerit arcu lorem, in tempor est posuere id." + self.titleView.font = UIFont.preferredFont(forTextStyle: .headline) + self.titleView.numberOfLines = 0 + + self.subtitleLabel.text = "Lorem ipsum dolor sit amet consectetur adipiscing elit" + self.subtitleLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) + self.subtitleLabel.numberOfLines = 0 + + self.iconView.layer.borderColor = UIColor.systemGray.cgColor + self.iconView.layer.borderWidth = 1 + + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var body: Layout { + HStack(alignment: .titleCenterAlignment) { + iconView + .resizable() + .frame(width: 20, height: 20) + .alignmentGuide(.titleCenterAlignment, computeValue: { d in d[.top] + d.height / 2 }) + Spacer(16) + VStack(alignment: .leading) { + titleView + .alignmentGuide(.titleCenterAlignment, computeValue: { d in d[.top] + d.height / 2 }) + subtitleLabel + } + Spacer() + } + .padding(16) + } +} + +@available(iOS 17, *) +#Preview { + FirstLabelAlignmentView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/FruitSelectorView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/FruitSelectorView.swift new file mode 100644 index 0000000..5e8eb7c --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/5-Alignment/FruitSelectorView.swift @@ -0,0 +1,119 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +private extension VerticalAlignment { + + private enum ArrowAlignment: AlignmentID { + static func defaultValue(in d: ViewDimensions) -> CGFloat { + /// The value here doesn't matter, as the ArrowAlignment is used just as identifier for alignmentGuides. + return 0 + } + } + + static let arrowAlignment = VerticalAlignment(ArrowAlignment.self) +} + +private extension HorizontalAlignment { + + private enum ListAlignment: AlignmentID { + static func defaultValue(in d: ViewDimensions) -> CGFloat { + /// The value here doesn't matter, as the ListAlignment is used just as identifier for alignmentGuides. + return 0 + } + } + + static let listAlignment = HorizontalAlignment(ListAlignment.self) +} + +@QuickLayout +final class FruitSelectorView: UIView { + + private let iconView = UIImageView(image: UIImage(systemName: "arrow.right.circle.fill")!) // swiftlint:disable:this force_unwrapping + private let labels: [UILabel] + private var selectedLabel: UILabel + + init() { + let words = ["Mango", "Strawberries", "Pineapple", "Watermelon", "Orange", "Apple", "Blueberries"] + + self.labels = words.map { word in + let label = UILabel() + label.text = word + label.textAlignment = .center + label.font = UIFont.preferredFont(forTextStyle: .title2) + return label + } + + selectedLabel = labels[1] + super.init(frame: .zero) + self.labels.forEach { label in + let gr = UITapGestureRecognizer(target: self, action: #selector(didTap(_:))) + label.isUserInteractionEnabled = true + label.addGestureRecognizer(gr) + } + } + + @objc + func didTap(_ sender: UITapGestureRecognizer) { + let label = sender.view + if let label = self.labels.first(where: { $0 === label }) { + selectedLabel = label + UIView.animate(withDuration: 0.33, delay: 0.0, options: .curveEaseInOut) { + self.setNeedsLayout() + self.layoutIfNeeded() + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var body: Layout { + + /// Using .arrowAlignment here makes HStack align the arrow icon with the center of the selected label. + HStack(alignment: .arrowAlignment, spacing: 10) { + + iconView + .alignmentGuide(.arrowAlignment) { d in + /// Define .arrowAlignment as the center of the icon. + d[VerticalAlignment.center] + } + + VStack(alignment: .leading, spacing: 8) { + ForEach(labels) { label in + if label === selectedLabel { + label + .alignmentGuide(.arrowAlignment) { d in + /// Define .arrowAlignment as the center the selected label. + d[VerticalAlignment.center] + } + } else { + label + } + } + } + .alignmentGuide(.listAlignment) { d in + /// Define .listAlignment as the horizontal center of the list. + /// This is to exclude the size of the iconView. + d[HorizontalAlignment.center] + } + } + .padding(16) + .alignmentGuide(HorizontalAlignment.center) { d in + /// Override HorizontalAlignment.center with .listAlignment + /// from the child VStack. This is to make the view container view align the list of labels in the center without including the size of the iconView. + d[.listAlignment] + } + } +} + +@available(iOS 17, *) +#Preview { + FruitSelectorView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/6-AdvanceSizing/AdvanceSizingContainerView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/6-AdvanceSizing/AdvanceSizingContainerView.swift new file mode 100644 index 0000000..e981231 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/6-AdvanceSizing/AdvanceSizingContainerView.swift @@ -0,0 +1,51 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +@QuickLayout +final class AdvanceSizingContainerView: UIView { + + private let exampleView = AdvanceSizingView(with: "This is an example view", image: UIImage(systemName: "lightbulb")) + private let sizeInfoOne = UILabel() + private let sizeInfoTwo = UILabel() + + init() { + let firstExampleSize = AdvanceSizingView.sizeThatFits(UIScreen.main.bounds.size, with: "Hi", image: UIImage(systemName: "hand.wave")) + sizeInfoOne.numberOfLines = 0 + sizeInfoOne.textAlignment = .center + sizeInfoOne.text = "Size with title 'Hi' and waving image. Width: \(firstExampleSize.width), height: \(firstExampleSize.height)" + + let secondExampleSize = AdvanceSizingView.sizeThatFits(UIScreen.main.bounds.size, with: "Hello World", image: nil) + sizeInfoTwo.numberOfLines = 0 + sizeInfoTwo.textAlignment = .center + sizeInfoTwo.text = "Size with title 'Hello World' and no image. Width: \(secondExampleSize.width), height: \(secondExampleSize.height)" + + super.init(frame: .zero) + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var body: Layout { + VStack(spacing: 12) { + exampleView + Spacer(20) + sizeInfoOne + sizeInfoTwo + } + .padding(20) + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + AdvanceSizingContainerView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/6-AdvanceSizing/AdvanceSizingView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/6-AdvanceSizing/AdvanceSizingView.swift new file mode 100644 index 0000000..27c032c --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/6-AdvanceSizing/AdvanceSizingView.swift @@ -0,0 +1,64 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +@QuickLayout +final class AdvanceSizingView: UIView { + + private let label = UILabel() + private let imageView = UIImageView() + private let separator = UIView() + + init(with text: String, image: UIImage?) { + label.text = text + imageView.image = image + separator.backgroundColor = .gray + separator.layer.cornerRadius = 1 + super.init(frame: .zero) + } + + override convenience init(frame: CGRect) { + self.init(with: "This is an example view", image: UIImage(systemName: "lightbulb")) + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var body: Layout { + VStack(spacing: 5) { + HStack(spacing: 8) { + label + imageView + } + separator + .frame(height: 2) + .frame(maxWidth: 220) + } + } + + // MARK: - Advance Sizing + + static func sizeThatFits(_ size: CGSize, with text: String, image: UIImage?) -> CGSize { + VStack(spacing: 5) { + HStack(spacing: 8) { + UILabel.proxy(for: text) + UIImageView.proxy(for: image) + } + ViewProxy(width: 220, height: 2) + } + .sizeThatFits(size) + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + AdvanceSizingView(with: "This is an example view", image: UIImage(systemName: "lightbulb")) +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/7-LazyView/LazyInstantiationView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/7-LazyView/LazyInstantiationView.swift new file mode 100644 index 0000000..ba4c846 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/7-LazyView/LazyInstantiationView.swift @@ -0,0 +1,129 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +@MainActor +@QuickLayout +final class LazyInstantiationView: UIView { + + private let firstLabel = LazyView { + let label = UILabel() + label.text = "One" + label.textColor = .systemOrange + label.font = .systemFont(ofSize: 20, weight: .bold) + return label + } + + private let secondLabel = LazyView { + let label = UILabel() + label.text = "Two" + label.textColor = .systemGreen + label.font = .systemFont(ofSize: 20, weight: .bold) + return label + } + + private let thirdLabel = LazyView { + let label = UILabel() + label.text = "Three" + label.textColor = .systemIndigo + label.font = .systemFont(ofSize: 20, weight: .bold) + return label + } + + private let addLabelButton = LazyView { + let button = UIButton(type: .system) + button.setTitle("Add", for: .normal) + return button + } + + private let removeLabelButton = LazyView { + let button = UIButton(type: .system) + button.setTitleColor(.systemRed, for: .normal) + button.setTitleColor(.systemGray, for: .disabled) + button.setTitle("Remove", for: .normal) + return button + } + + private let descriptionLabel = LazyView { + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + return label + } + + private var numberOfLabels = 0 + + override init(frame: CGRect) { + super.init(frame: frame) + addLabelButton.loadIfNeeded().addTarget(self, action: #selector(addLabel), for: .touchUpInside) + removeLabelButton.loadIfNeeded().addTarget(self, action: #selector(removeLabel), for: .touchUpInside) + updateState() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + var body: Layout { + VStack(spacing: 16) { + HStack(spacing: 8) { + if numberOfLabels > 0 { + firstLabel + } + if numberOfLabels > 1 { + secondLabel + } + if numberOfLabels > 2 { + thirdLabel + } + } + HStack(spacing: 8) { + removeLabelButton + addLabelButton + } + descriptionLabel + } + } + + @objc private func addLabel() { + updateNumberOfLabels(to: numberOfLabels + 1) + } + + @objc private func removeLabel() { + updateNumberOfLabels(to: numberOfLabels - 1) + } + + private func updateNumberOfLabels(to value: Int) { + numberOfLabels = value + setNeedsLayout() + layoutIfNeeded() + updateState() + } + + private func updateState() { + addLabelButton.loadIfNeeded().isEnabled = numberOfLabels < 3 + removeLabelButton.loadIfNeeded().isEnabled = numberOfLabels > 0 + let description = """ + First loaded: \(firstLabel.isLoaded ? "✅" : "❌") + Second loaded: \(secondLabel.isLoaded ? "✅" : "❌") + Third loaded: \(thirdLabel.isLoaded ? "✅" : "❌") + """ + let attribtuedString = NSMutableAttributedString(string: description) + let style = NSMutableParagraphStyle() + style.lineSpacing = 12 + attribtuedString.addAttribute(.paragraphStyle, value: style, range: .init(location: 0, length: description.count)) + descriptionLabel.loadIfNeeded().attributedText = attribtuedString + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + LazyInstantiationView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/8-MobileConfigMigrationView/MobileConfigMigrationView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/8-MobileConfigMigrationView/MobileConfigMigrationView.swift new file mode 100644 index 0000000..e07a842 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/8-MobileConfigMigrationView/MobileConfigMigrationView.swift @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +@MainActor +@QuickLayout +final class MobileConfigMigrationView: UIView { + + private var isBodyEnabledFeatureFlag = false + + private lazy var bodyEnabledLabel = { + let label = UILabel() + label.text = "Body is enabled ✅" + label.textAlignment = .center + return label + }() + + private lazy var explainerLabel = { + let label = UILabel() + label.text = "Enable body by toggling the switch below." + label.numberOfLines = 0 + label.textColor = .secondaryLabel + label.textAlignment = .center + return label + }() + + private lazy var bodyEnabledButton = { + let bodySwitch = UISwitch() + bodySwitch.addTarget(self, action: #selector(self.switchToggled), for: .valueChanged) + bodySwitch.sizeToFit() + return bodySwitch + }() + + var body: Layout { + VStack { + bodyEnabledLabel + Spacer(10) + explainerLabel + Spacer(10) + bodyEnabledButton + } + } + + override var isBodyEnabled: Bool { + isBodyEnabledFeatureFlag + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + if isBodyEnabled { + return body.sizeThatFits(size) + } else { + // Calculate size in the imperative way. + return size + } + } + + override func layoutSubviews() { + super.layoutSubviews() + if !isBodyEnabled { + imperativeLayout() + } + } + + private func imperativeLayout() { + addSubview(explainerLabel) + addSubview(bodyEnabledButton) + let labelSize = explainerLabel.sizeThatFits(self.bounds.size) + explainerLabel.bounds = CGRect(origin: .zero, size: labelSize) + explainerLabel.center = CGPoint(x: bounds.midX, y: bounds.midY - 4) + bodyEnabledButton.center = CGPoint(x: bounds.midX, y: explainerLabel.frame.maxY + 10 + bodyEnabledButton.bounds.height / 2) + } + + @objc private func switchToggled() { + isBodyEnabledFeatureFlag.toggle() + setNeedsLayout() + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + MobileConfigMigrationView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/9-LayoutContainers/GridLayoutExampleView.swift b/Sources/QuickLayout/QuickLayout/__showcase__/9-LayoutContainers/GridLayoutExampleView.swift new file mode 100644 index 0000000..5980784 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/9-LayoutContainers/GridLayoutExampleView.swift @@ -0,0 +1,380 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout + +/// Interactive Grid Layout Example +/// **Key concepts**: +/// - Grid and GridRow layouts +/// - Dynamic spacing controls with sliders +/// - Font size scaling with real-time updates +/// - Real-time layout updates +/// - Interactive UI elements +@QuickLayout +final class GridLayoutExampleView: UIView { + + private var horizontalSpacing: CGFloat = 8 + private var verticalSpacing: CGFloat = 8 + private var fontScale: CGFloat = 1.0 + + private lazy var slider1: UISlider = { + let slider = UISlider() + slider.minimumValue = 0 + slider.maximumValue = 40 + slider.value = Float(horizontalSpacing) + slider.isContinuous = true + slider.minimumTrackTintColor = UIColor(red: 0.2, green: 0.6, blue: 1.0, alpha: 1.0) + slider.maximumTrackTintColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0) + slider.thumbTintColor = UIColor(red: 0.1, green: 0.4, blue: 0.8, alpha: 1.0) + slider.addTarget(self, action: #selector(horizontalSliderChanged), for: .valueChanged) + return slider + }() + + private lazy var slider2: UISlider = { + let slider = UISlider() + slider.minimumValue = 0 + slider.maximumValue = 40 + slider.value = Float(verticalSpacing) + slider.isContinuous = true + slider.minimumTrackTintColor = UIColor(red: 0.2, green: 0.6, blue: 1.0, alpha: 1.0) + slider.maximumTrackTintColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0) + slider.thumbTintColor = UIColor(red: 0.1, green: 0.4, blue: 0.8, alpha: 1.0) + slider.addTarget(self, action: #selector(verticalSliderChanged), for: .valueChanged) + return slider + }() + + private lazy var fontSizeSlider: UISlider = { + let slider = UISlider() + slider.minimumValue = 0.5 + slider.maximumValue = 2.0 + slider.value = Float(fontScale) + slider.isContinuous = true + slider.minimumTrackTintColor = UIColor(red: 0.2, green: 0.6, blue: 1.0, alpha: 1.0) + slider.maximumTrackTintColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0) + slider.thumbTintColor = UIColor(red: 0.1, green: 0.4, blue: 0.8, alpha: 1.0) + slider.addTarget(self, action: #selector(fontSizeSliderChanged), for: .valueChanged) + return slider + }() + + // Header labels + private let item: UILabel = { + let label = UILabel() + label.text = "Item" + label.font = .boldSystemFont(ofSize: 18) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let itemDescription: UILabel = { + let label = UILabel() + label.text = "Description" + label.font = .boldSystemFont(ofSize: 18) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let quantity: UILabel = { + let label = UILabel() + label.text = "Quantity" + label.font = .boldSystemFont(ofSize: 18) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let price: UILabel = { + let label = UILabel() + label.text = "Price" + label.font = .boldSystemFont(ofSize: 18) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + // Data rows + private let orange: UILabel = { + let label = UILabel() + label.text = "🍊 Orange" + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let orangeDescription: UILabel = { + let label = UILabel() + label.text = "Sweet and tangy orange" + label.font = .systemFont(ofSize: 15) + label.textColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let orangeQuantity: UILabel = { + let label = UILabel() + label.text = "Qty: 4" + label.font = .systemFont(ofSize: 15) + label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let orangePrice: UILabel = { + let label = UILabel() + label.text = "£1.30" + label.font = .systemFont(ofSize: 16, weight: .semibold) + label.textColor = UIColor(red: 0.2, green: 0.6, blue: 0.2, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let apple: UILabel = { + let label = UILabel() + label.text = "🍎 Apple" + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let appleDescription: UILabel = { + let label = UILabel() + label.text = "Juicy green apple" + label.font = .systemFont(ofSize: 15) + label.textColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let appleQuantity: UILabel = { + let label = UILabel() + label.text = "Qty: 4" + label.font = .systemFont(ofSize: 15) + label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let applePrice: UILabel = { + let label = UILabel() + label.text = "£2.05" + label.font = .systemFont(ofSize: 16, weight: .semibold) + label.textColor = UIColor(red: 0.2, green: 0.6, blue: 0.2, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let banana: UILabel = { + let label = UILabel() + label.text = "🍌 Banana" + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let bananaDescription: UILabel = { + let label = UILabel() + label.text = "Fresh yellow bananas" + label.font = .systemFont(ofSize: 15) + label.textColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let bananaQuantity: UILabel = { + let label = UILabel() + label.text = "Qty: 4" + label.font = .systemFont(ofSize: 15) + label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let bananaPrice: UILabel = { + let label = UILabel() + label.text = "£1.50" + label.font = .systemFont(ofSize: 16, weight: .semibold) + label.textColor = UIColor(red: 0.2, green: 0.6, blue: 0.2, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private let totalPrice: UILabel = { + let label = UILabel() + label.text = "Total: £5.85" + label.font = .boldSystemFont(ofSize: 18) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + // Control labels + private var horizontalSpacingLabel: UILabel = { + let label = UILabel() + label.text = "↔️ Horizontal Spacing: 8" + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private var verticalSpacingLabel: UILabel = { + let label = UILabel() + label.text = "↕️ Vertical Spacing: 8" + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + private var fontSizeLabel: UILabel = { + let label = UILabel() + label.text = "🔤 Font Size: 100%" + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = UIColor(red: 0.2, green: 0.3, blue: 0.5, alpha: 1.0) + label.numberOfLines = 0 + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.97, alpha: 1.0) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Actions + + @objc private func horizontalSliderChanged(_ sender: UISlider) { + horizontalSpacing = CGFloat(sender.value) + horizontalSpacingLabel.text = "↔️ Horizontal Spacing: \(Int(horizontalSpacing))" + setNeedsLayout() + } + + @objc private func verticalSliderChanged(_ sender: UISlider) { + verticalSpacing = CGFloat(sender.value) + verticalSpacingLabel.text = "↕️ Vertical Spacing: \(Int(verticalSpacing))" + setNeedsLayout() + } + + @objc private func fontSizeSliderChanged(_ sender: UISlider) { + fontScale = CGFloat(sender.value) + fontSizeLabel.text = "🔤 Font Size: \(Int(fontScale * 100))%" + updateAllFontSizes() + setNeedsLayout() + } + + private func updateAllFontSizes() { + // Header labels + item.font = .boldSystemFont(ofSize: 18 * fontScale) + itemDescription.font = .boldSystemFont(ofSize: 18 * fontScale) + quantity.font = .boldSystemFont(ofSize: 18 * fontScale) + price.font = .boldSystemFont(ofSize: 18 * fontScale) + + // Data rows + orange.font = .systemFont(ofSize: 16 * fontScale, weight: .medium) + orangeDescription.font = .systemFont(ofSize: 15 * fontScale) + orangeQuantity.font = .systemFont(ofSize: 15 * fontScale) + orangePrice.font = .systemFont(ofSize: 16 * fontScale, weight: .semibold) + + apple.font = .systemFont(ofSize: 16 * fontScale, weight: .medium) + appleDescription.font = .systemFont(ofSize: 15 * fontScale) + appleQuantity.font = .systemFont(ofSize: 15 * fontScale) + applePrice.font = .systemFont(ofSize: 16 * fontScale, weight: .semibold) + + banana.font = .systemFont(ofSize: 16 * fontScale, weight: .medium) + bananaDescription.font = .systemFont(ofSize: 15 * fontScale) + bananaQuantity.font = .systemFont(ofSize: 15 * fontScale) + bananaPrice.font = .systemFont(ofSize: 16 * fontScale, weight: .semibold) + + totalPrice.font = .boldSystemFont(ofSize: 20 * fontScale) + } + + // MARK: - Layout + + var body: Layout { + VStack { + + Spacer() + + Grid( + alignment: .leading, + horizontalSpacing: horizontalSpacing, + verticalSpacing: verticalSpacing + ) { + GridRow { + item + itemDescription + quantity + price + } + + GridRow { + orange + orangeDescription + orangeQuantity + orangePrice + } + + GridRow { + apple + appleDescription + appleQuantity + applePrice + } + + GridRow { + banana + bananaDescription + bananaQuantity + bananaPrice + } + + GridRow { + totalPrice + } + } + .padding(.horizontal, 16) + .frame(width: 400) + + Spacer() + + Grid(alignment: .leading) { + + GridRow { + horizontalSpacingLabel + .layoutPriority(1) + slider1 + } + + GridRow { + verticalSpacingLabel + slider2 + } + + GridRow { + fontSizeLabel + fontSizeSlider + } + } + .padding(.horizontal, 16) + .padding(.bottom, 32) + } + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + GridLayoutExampleView() +} diff --git a/Sources/QuickLayout/QuickLayout/__showcase__/QuickLayoutShowcaseApp.swift b/Sources/QuickLayout/QuickLayout/__showcase__/QuickLayoutShowcaseApp.swift new file mode 100644 index 0000000..ba4d014 --- /dev/null +++ b/Sources/QuickLayout/QuickLayout/__showcase__/QuickLayoutShowcaseApp.swift @@ -0,0 +1,178 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayout +import SwiftUI + +@main +struct QuickLayoutShowcaseApp: App { + + var body: some Scene { + WindowGroup { + // "No seriously, I need to debug my Declarative Layout view and i need an app that loads it!" + // + // Ok, ok, In that case use our ShowcaseViewRepresentable and pass to it your DeclarativeLayout View + // and replace the ShowcaseApp() like this: + // + // ShowcaseViewRepresentable(MyView.self) + // + ShowcaseApp() + } + } +} + +struct ShowcaseApp: View { + private let showcases = [ + SCSection( + "Hello World", + [ + HelloWorldView.self + ]), + SCSection( + "State Management View", + [ + StateManagementView.self + ]), + + SCSection( + "Alignment", + [ + AlignmentGuidesView.self, + CustomAlignmentView.self, + EmojiAlignmentView.self, + FirstLabelAlignmentView.self, + FruitSelectorView.self, + ]), + SCSection( + "Advance Sizing", + [ + AdvanceSizingContainerView.self, + AdvanceSizingView.self, + ]), + SCSection( + "Lazy View", + [ + LazyInstantiationView.self + ]), + SCSection( + "Mobile Config Migration View", + [ + MobileConfigMigrationView.self + ]), + SCSection( + "Layout Containers", + [ + GridLayoutExampleView.self + ]), + ] + + var body: some View { + NavigationView { + List { + VStack(alignment: .leading, spacing: 10) { + Text("QuickLayout Demo") + .font(.largeTitle) + .fontWeight(.bold) + Text("Explore all major QuickLayout features in this interactive demo app or instantly preview your changes using Xcode Preview.") + .font(.body) + } + .multilineTextAlignment(.leading) + .listRowSeparator(.hidden) + + ForEach(showcases) { section in + Section(section.title) { + ForEach(section.showscases) { showcase in + NavigationLink(destination: showcase.view()) { + Text(showcase.label) + } + .listRowSeparator(.hidden) + } + } + } + } + .listStyle(.plain) + } + } +} + +private struct ShowcaseViewRepresentable: UIViewRepresentable { + + let view: UIView.Type + + init(_ viewType: UIView.Type) { + self.view = viewType + } + + func makeUIView(context: Context) -> some UIView { + view.init() as UIView + } + + func updateUIView(_ uiView: UIViewType, context: Context) { + } +} + +private struct SCShowcase: Identifiable, Hashable { + let id = UUID() + + let viewType: UIView.Type + let label: String + + init(viewType: UIView.Type) { + self.viewType = viewType + self.label = String(describing: viewType).splittingCamelCase() + } + + @MainActor + func view() -> some View { + ShowcaseViewRepresentable(viewType) + } + + static func == (lhs: SCShowcase, rhs: SCShowcase) -> Bool { + lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} + +private extension String { + func splittingCamelCase() -> String { + let stripped = self.hasSuffix("View") ? String(self.dropLast(4)) : self + + // Split by uppercase letters + let words = stripped.reduce("") { result, char in + guard !result.isEmpty else { return String(char) } + return char.isUppercase ? result + " " + String(char) : result + String(char) + } + + // Filter out single capital letters (abbreviations) + let filtered = words.split(separator: " ") + .filter { $0.count > 1 || $0.first?.isUppercase == false } + .joined(separator: " ") + + return String(filtered).trimmingCharacters(in: .whitespaces) + } +} + +private struct SCSection: Identifiable, Hashable { + let id = UUID() + let showscases: [SCShowcase] + let title: String + + init(_ title: String, _ showcases: [UIView.Type]) { + self.title = title + self.showscases = showcases.map { SCShowcase(viewType: $0) } + } +} + +// MARK: Preview code + +@available(iOS 17, *) +#Preview { + ShowcaseApp() +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/LayoutBuilder.swift b/Sources/QuickLayout/QuickLayoutBridge/LayoutBuilder.swift new file mode 100644 index 0000000..b99e477 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/LayoutBuilder.swift @@ -0,0 +1,59 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import QuickLayoutCore +import UIKit + +// A custom parameter attribute that constructs layout from closures. +@resultBuilder +public struct LayoutBuilder { + + public static func buildBlock(_ layout: Layout) -> Layout { + layout + } + + public static func buildExpression(_ layout: Layout) -> Layout { + layout + } + + public static func buildExpression(_ layout: Layout?) -> Layout { + layout ?? EmptyLayout() + } + + public static func buildExpression(_ view: UIView) -> Layout { + SingleElement(child: view) + } + + public static func buildExpression(_ view: UIView?) -> Layout { + view.map { SingleElement(child: $0) } ?? EmptyLayout() + } + + public static func buildExpression(_ view: Element) -> Layout { + SingleElement(child: view) + } + + public static func buildExpression(_ view: Element?) -> Layout { + view.map { SingleElement(child: $0) } ?? EmptyLayout() + } + + public static func buildLimitedAvailability(_ layout: Layout) -> Layout { + layout + } + + public static func buildEither(first: Layout) -> Layout { + first + } + + public static func buildEither(second: Layout) -> Layout { + second + } + + public static func buildOptional(_ layout: Layout?) -> Layout { + layout ?? EmptyLayout() + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/LazyView.swift b/Sources/QuickLayout/QuickLayoutBridge/LazyView.swift new file mode 100644 index 0000000..c817a4d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/LazyView.swift @@ -0,0 +1,91 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutCore + +public struct LazyView { + // MARK: - Setup + private final class DeferredView { + private enum State { + case deferred(_ initializer: () -> ViewType) + case resolved(value: ViewType) + } + + private var state: State + + init(_ initializer: @escaping () -> ViewType) { + self.state = .deferred(initializer) + } + + var resolved: ViewType { + switch state { + case .deferred(let initializer): + let result = initializer() + state = .resolved(value: result) + return result + case .resolved(let value): + return value + } + } + + var ifResolved: ViewType? { + switch state { + case .deferred: + return nil + case .resolved(let value): + return value + } + } + + var isResolved: Bool { + switch state { + case .deferred: + return false + case .resolved: + return true + } + } + } + + private let view: DeferredView + + // MARK: - API + + public init(_ view: @escaping () -> ViewType) { + self.view = DeferredView(view) + } + + public var ifLoaded: ViewType? { + view.ifResolved + } + + public var isLoaded: Bool { + view.isResolved + } + + @discardableResult public func loadIfNeeded() -> ViewType { + view.resolved + } +} + +extension LazyView: Element where ViewType: Element { + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + loadIfNeeded().quick_layoutThatFits(proposedSize) + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + loadIfNeeded().quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + loadIfNeeded().quick_layoutPriority() + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + loadIfNeeded().quick_extractViewsIntoArray(&views) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/QuickLayout.swift b/Sources/QuickLayout/QuickLayoutBridge/QuickLayout.swift new file mode 100644 index 0000000..f8e6c4c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/QuickLayout.swift @@ -0,0 +1,334 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@_exported import FastResultBuilder +@_exported import QuickLayoutCore +import UIKit + +/// Arranges its elements in a horizontal line. Spacing is added between each pair of elements. +/// If spacing is 0, the elements are stacked without space between them. +/// If there is a Spacer(x) or Spacer() between two views, the "spacing" parameter will be ignored for those two views. +public func HStack( + alignment: VerticalAlignment = .center, + spacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) -> StackElement { + StackElement.horizontalStack(children: children(), spacing: spacing, alignment: alignment) +} + +/// Arranges its elements in a vertical line. Spacing is added between each pair of elements. +/// If spacing is 0, the elements are stacked without space between them. +/// If there is a Spacer(x) or Spacer() between two views, the "spacing" parameter will be ignored for those two views. +public func VStack( + alignment: HorizontalAlignment = .center, + spacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) -> StackElement { + StackElement.verticalStack(children: children(), spacing: spacing, alignment: alignment) +} + +/// Overlays its elements, aligning them in both axes. +/// ZStack proposes each child the parent's size, takes the size of the largest child, and then aligns each child within that size. +/// See also .overlay modifier. +public func ZStack( + alignment: Alignment = .center, + @FastArrayBuilder children: () -> [Element] +) -> Element & Layout { + ZStackElement(children: children(), alignment: alignment) +} + +/// Arranges its elements in a horizontal flow layout, wrapping to a new line when the current line exceeds the bounding space. +public func HFlow( + itemAlignment: VerticalAlignment = .center, + lineAlignment: HorizontalAlignment = .center, + itemSpacing: CGFloat = 0, + lineSpacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) -> Element & Layout { + FlowElement(children: children(), mainAxis: .horizontal, itemSpacing: itemSpacing, lineSpacing: lineSpacing, itemAlignmentID: itemAlignment.alignmentID, lineAlignmentID: lineAlignment.alignmentID) +} + +/// Arranges its elements in a vertical flow layout, wrapping to a new column when the current column exceeds the bounding space. +/// `itemSpacing` parameter controls the spacing between items on the same column. +/// `lineSpacing` parameter controls the spacing between columns. +/// `itemAlignment` parameter aligns items horizontally within their column. +/// `lineAlignment` parameter aligns columns vertically within the overall layout. +public func VFlow( + itemAlignment: HorizontalAlignment = .center, + lineAlignment: VerticalAlignment = .center, + itemSpacing: CGFloat = 0, + lineSpacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) -> Element & Layout { + FlowElement(children: children(), mainAxis: .vertical, itemSpacing: itemSpacing, lineSpacing: lineSpacing, itemAlignmentID: itemAlignment.alignmentID, lineAlignmentID: lineAlignment.alignmentID) +} + +/// Arranges its elements in a two-dimensional grid with rows and columns. +/// `horizontalSpacing` parameter controls the spacing between columns. +/// `verticalSpacing` parameter controls the spacing between rows. +/// `alignment` parameter aligns elements within their cells. +/// Each row can specify its own vertical alignment, and individual cells can override alignment using gridCellAnchor modifier. +public func Grid( + alignment: Alignment = .center, + horizontalSpacing: CGFloat = 0, + verticalSpacing: CGFloat = 0, + @FastArrayBuilder rows: () -> [GridRowElement] = { [] }, +) -> Element & Layout { + GridElement(rows: rows(), alignment: alignment, horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing) +} + +/// Represents a single row in a Grid layout. +/// Each GridRow contains a horizontal collection of elements that form the cells in that row. +/// `alignment` parameter controls the vertical alignment of elements within a specific row. +public func GridRow( + alignment: VerticalAlignment? = nil, + @FastArrayBuilder children: () -> [Element] +) -> GridRowElement { + GridRowElement(children: children(), alignment: alignment) +} + +/// A flexible space that expands along the main axis of its containing stack. +public func Spacer() -> Element { + SpacerElement() +} + +/// A fixed length spacer. The length is applied only for the main axis of its containing stack. +public func Spacer(_ length: CGFloat) -> Element { + SpacerElement(length: length) +} + +public func Spacer(minLength: CGFloat) -> FastExpression { + BlockExpression(expressions: [ValueExpression(value: Spacer()), ValueExpression(value: SpacerElement(length: minLength))]) +} + +/// Null object pattern. Sizes to zero. +public func EmptyLayout() -> Element & Layout { + EmptyElement() +} + +public extension Element { + + /// Constrains the elements’s dimensions to an aspect ratio specified by a CGSize. + /// If this view is resizable, it uses aspectRatio as its own aspect ratio. + func aspectRatio(_ ratio: CGSize, contentMode: ContentMode = .fit) -> Element & Layout { + AspectRatioElement(child: self, aspectRatio: ratio.width / ratio.height, contentMode: contentMode) + } + + /// Adds the desired amount of space to the edges of this element. + /// The leading and trailing margins are applied appropriately to the left or right margins based on the current layout direction. + /// For example, the leading margin is applied to the right edge of the view in right-to-left layouts. + func padding(_ edges: EdgeSet = .all, _ length: CGFloat) -> Element & Layout { + PaddingElement(child: self, edges: edges, length: length) + } + + /// Adds the desired amount of space to the edges of this element. + /// The leading and trailing margins are applied appropriately to the left or right margins based on the current layout direction. + /// For example, the leading margin is applied to the right edge of the view in right-to-left layouts. + func padding(_ insets: EdgeInsets) -> Element & Layout { + PaddingElement(child: self, insets: insets) + } + + /// Adds the desired amount of space to all edges of this element. + func padding(_ length: CGFloat) -> Element & Layout { + PaddingElement(child: self, edges: .all, length: length) + } + + func padding(ignoringLayoutDirection insets: UIEdgeInsets) -> Element & Layout { + PaddingElement(child: self, insets: insets) + } + + /// Offsets the child element by the specified amount. + func offset(x: CGFloat = 0, y: CGFloat = 0) -> Element & Layout { + OffsetElement(child: self, offset: CGPoint(x: x, y: y)) + } + + /// Fixed Frame Modifier + /// Positions the child element within an invisible container with the specified size. + /// The modifier doesn’t change the size of its child element but acts more like a “picture frame” by letting the child be any size it wants and only positioning it inside its own bounds. + /// If you want to "assign" a size to the child view, use the view.resizable().frame(width: l1, height: l2) modifier. + /// If you omit a constraint (or pass nil), the frame will use the sizing behavior of its child element for that axis. + /// When using this method, you must provide at least one size constraint. + /// + /// Negative, nan, and infinite values are ignored. + func frame( + width: CGFloat? = nil, + height: CGFloat? = nil, + alignment: Alignment = .center + ) -> Element & Layout { + FixedFrameElement( + child: self, + width: width, + height: height, + alignment: alignment + ) + } + + /// Flexible Frame Modifier + /// Positions the child element inside an invisible, flexible container. + /// The modifier doesn’t change the size of its child element but acts more like a “picture frame” by letting the child be any size it wants and only positioning it inside its own bounds. + /// If you omit a constraint (or pass nil), the frame will use the sizing behavior of its child element for that axis. + /// When using this method, you must provide at least one size constraint. + func frame( + minWidth: CGFloat? = nil, + maxWidth: CGFloat? = nil, + minHeight: CGFloat? = nil, + maxHeight: CGFloat? = nil, + alignment: Alignment = .center + ) -> Element & Layout { + FlexibleFrameElement( + child: self, + minWidth: minWidth, + maxWidth: maxWidth, + minHeight: minHeight, + maxHeight: maxHeight, + alignment: alignment + ) + } + + /// Fixed Size Modifier + /// Replaces the proposed size of the child with the infinity. + func fixedSize(axis: AxisSet = [.horizontal, .vertical]) -> Element & Layout { + FixedSizeElement(child: self, horizontal: axis.contains(.horizontal), vertical: axis.contains(.vertical)) + } + + /// Sets the priority by which a parent V/HStack should allocate space to this child. + /// A view's default priority is 0, which distributes available space evenly to all sibling views. + /// Set a higher priority to measure the view first with a larger portion of available space. + func layoutPriority(_ priority: CGFloat) -> Element { + LayoutPriorityElement(child: self, layoutPriority: priority) + } + + /// Overrides the default layout direction. + /// Doesn't affect the layout direction of child views. + func layoutDirection(_ direction: LayoutDirection) -> Element & Layout { + LayoutDirectionElement(child: self, layoutDirection: direction) + } + + /// Positions the content view within the frame of the target view. + /// The modifier measures the target view, proposes the target view's size to the content view, and then aligns it within the frame of the target view. + /// See also ZStack. + func overlay(alignment: Alignment = .center, @LayoutBuilder content: () -> Element?) -> Element & Layout { + LayeringElement( + target: self, + layer: content() ?? EmptyLayout(), + type: .overlay, + alignment: alignment + ) + } + + /// Positions the content view within the frame of the target view. + /// The modifier measures the target view, proposes the target view's size to the content view, and then aligns it within the frame of the target view. + /// See also ZStack. + func background(alignment: Alignment = .center, @LayoutBuilder content: () -> Element?) -> Element & Layout { + LayeringElement( + target: self, + layer: content() ?? EmptyLayout(), + type: .background, + alignment: alignment + ) + } + + /// When the specified horizontal alignment is being applied, the child + /// element will be positioned according to the computed alignment value. + func alignmentGuide(_ horizontalAlignment: HorizontalAlignment, computeValue: @escaping @Sendable (ViewDimensions) -> CGFloat) -> Element & Layout { + AlignmentGuideElement(child: self, horizontalAlignment: horizontalAlignment, computeValue: computeValue) + } + + /// When the specified vertical alignment is being applied, the child + /// element will be positioned according to the computed alignment value. + func alignmentGuide(_ verticalAlignment: VerticalAlignment, computeValue: @escaping @Sendable (ViewDimensions) -> CGFloat) -> Element & Layout { + AlignmentGuideElement(child: self, verticalAlignment: verticalAlignment, computeValue: computeValue) + } + + /// Grid cell Anchor Modifier + /// Positions the child view within the cell of the grid. + /// The modifier doesn’t change the size of its child directly but lets the child be + /// any size it wants and only positioning it inside its own bounds. + /// The modifier will always override the alignment set by the row or column. + func gridCellAnchor(_ alignment: Alignment = .center) -> Element & Layout { + GridCellAnchorElement(child: self, alignment: alignment) + } + + func gridCellAnchor(_ unitPoint: UnitPoint) -> Element & Layout { + GridCellAnchorElement(child: self, unitPoint: unitPoint) + } + + /// Grid Column Alignment Modifier + /// Overrides the default horizontal alignment of a grid column. + /// The alignment will be applied to all elements in the same column as this element. + func gridColumnAlignment(_ alignment: HorizontalAlignment = .center) -> Element & Layout { + GridColumnAlignmentElement(child: self, alignment: alignment) + } +} + +public extension LeafElement { + + /// Makes the view ignore its intrinsic size (size returned by sizeThatFits) so it becomes fully flexible along both axes. + /// If only one axis is specified, the target view intrinsic size is preserved along the other axis. + func resizable(axis: AxisSet = [.horizontal, .vertical]) -> Element & Layout { + ResizableElement(child: self, axis: axis) + } + + /// Measures the intrinsic size of the view and then adds the additional size. + /// Nans and infinite values are ignored. + func expand(by size: CGSize) -> Element & Layout { + ExpandableElement(child: self, size: size) + } +} + +extension StackElement { + + /// Ideal Layout Modifier + /// If enabled, the stack use the ideal layout algorithm to make children have equal size along the cross axis. + public func idealLayout(_ enabled: Bool = true) -> StackElement { + StackElement( + children: children, + mainAxis: mainAxis, + spacing: spacing, + alignmentID: alignmentID, + idealLayout: enabled + ) + } +} + +extension UIView { + + /// Adds the given views as subviews of this view. + public func addSubviews(@FastArrayBuilder views: () -> [UIView]) { + let viewList = views + for v in viewList() { + addSubview(v) + } + } +} + +public func ForEach(_ list: [Element]) -> FastExpression { + BlockExpression(expressions: list.map { ValueExpression(value: $0) }) +} + +public func ForEach(_ list: [Element], map: (Element) -> Element) -> FastExpression { + BlockExpression(expressions: list.map { ValueExpression(value: map($0)) }) +} + +public func ForEach(_ list: [UIView], map: (UIView) -> Element) -> FastExpression { + BlockExpression(expressions: list.map { ValueExpression(value: map($0)) }) +} + +public typealias Alignment = QuickLayoutCore.Alignment +public typealias AlignmentID = QuickLayoutCore.AlignmentID +public typealias Axis = QuickLayoutCore.Axis +public typealias ContentMode = QuickLayoutCore.ContentMode +public typealias EdgeInsets = QuickLayoutCore.EdgeInsets +public typealias Element = QuickLayoutCore.Element +public typealias LeafElement = QuickLayoutCore.LeafElement +public typealias Flexibility = QuickLayoutCore.Flexibility +public typealias HorizontalAlignment = QuickLayoutCore.HorizontalAlignment +public typealias Layout = QuickLayoutCore.Layout +public typealias LayoutDirection = QuickLayoutCore.LayoutDirection +public typealias VerticalAlignment = QuickLayoutCore.VerticalAlignment +public typealias ViewDimensions = QuickLayoutCore.ElementDimensions +public typealias StackElement = QuickLayoutCore.StackElement diff --git a/Sources/QuickLayout/QuickLayoutBridge/QuickLayoutMacroDeclaration.swift b/Sources/QuickLayout/QuickLayoutBridge/QuickLayoutMacroDeclaration.swift new file mode 100644 index 0000000..8a5c6e3 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/QuickLayoutMacroDeclaration.swift @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + @QuickLayout is the recommended way to use QuickLayout. Adding it to a view will require that + you define a `body` var that returns the layout of your view. @QuickLayout views do not require + manual addition or removal of views, this is handled automatically (the body implicitly defines the + views that should be present on the view hierarchy). Layout and sizing are also handled automatically; + the only requirement is a body definition. +*/ +@attached(member, names: named(willMove(toWindow:)), named(layoutSubviews), named(sizeThatFits), named(quick_flexibility(for:))) +@attached(memberAttribute) +@attached(extension, conformances: HasBody) +public macro QuickLayout() = #externalMacro(module: "QuickLayoutMacro", type: "QuickLayout") + +/** + This macro is an implementation detail of @QuickLayout. It is not intended to be used directly. +*/ +@attached(body) +public macro _QuickLayoutInjection(_ value: String) = #externalMacro(module: "QuickLayoutMacro", type: "QuickLayoutInjection") diff --git a/Sources/QuickLayout/QuickLayoutBridge/README.md b/Sources/QuickLayout/QuickLayoutBridge/README.md new file mode 100644 index 0000000..1517184 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/README.md @@ -0,0 +1,10 @@ +# QuickLayout + +QuickLayout is a declarative layout library, built to support the adaptive layout needs of UIKit-first iOS apps. +This library adheres to the following core principles: + +- Optional: It is not a replacement for UIKit, it is a simple abstraction allowing layout to be expressed declaratively. +- Simple: The API surface is extremely minimal, with concepts that are very familiar for most iOS engineers. +- Helpful: The minimal API we have helps solve real problems, and should make building UI feel delightful. + +QuickLayout should stay true to these core values. It should not aim to solve problems outside the scope of UIKit layout, or add unnecessary features. diff --git a/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/BodyAppearanceCoordinator.swift b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/BodyAppearanceCoordinator.swift new file mode 100644 index 0000000..983674f --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/BodyAppearanceCoordinator.swift @@ -0,0 +1,125 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import UIKit + +private let appearanceAnimationKey = "BodyAppearanceCoordinator-appearance" +private let disappearanceAnimationKey = "BodyAppearanceCoordinator-disappearance" +private let disappearanceAnimationIDKey = "BodyAppearanceCoordinator-disappearance-id" + +@MainActor +final class BodyAppearanceCoordinator: NSObject, @preconcurrency CAAnimationDelegate { + private var disappearanceAnimationID: Int = 0 + private var appearingViews: Set = [] + private var activeViews: Set = [] + private var appearingViewAnimationKeys: [UIView: Set] = [:] + private var disappearingViewAnimations: [Int: Weak] = [:] + private var disableViewAppearanceAnimations: Bool = false + private weak var targetWindow: UIWindow? + + // MARK: - API + + func coordinateMove(to newWindow: UIWindow?) { + targetWindow = newWindow + if newWindow != nil { + disableViewAppearanceAnimations = true + // When moving window, we don't want to animate view appearance. + // The animations applied by this class are intended for transitions + // between onscreen view states. Applying animations as the body + // itself moves on and offscreen is not intended; these animations + // are external to the body and applying additional animations + // would not be expected. + DispatchQueue.main.async { [weak self] in + guard self?.targetWindow == newWindow else { return } + self?.disableViewAppearanceAnimations = false + } + } else { + disableViewAppearanceAnimations = false + } + } + + func coordinateAppearance(of view: UIView, in superview: UIView, index: Int) { + appearingViews.insert(view) + activeViews.insert(view) + superview.insertSubview(view, at: index) + animateAppearanceIfNeeded(for: view) + } + + func coordinateDisappearance(of view: UIView) { + appearingViews.remove(view) + activeViews.remove(view) + performDisappearanceWithAnimationIfNeeded(for: view) + } + + func beginViewAppearanceUpdates() { + appearingViewAnimationKeys = appearingViews.reduce(into: [:]) { partialResult, view in + partialResult[view] = view.animationKeys + } + } + + func commitViewAppearanceUpdates() { + for (view, existingAnimationKeys) in appearingViewAnimationKeys { + let disallowedAnimationKeys = view.animationKeys.subtracting(existingAnimationKeys) + view.removeAnimations(for: disallowedAnimationKeys) + } + appearingViews.removeAll() + appearingViewAnimationKeys.removeAll() + } + + // MARK: - CAAnimationDelegate + + func animationDidStop(_ animation: CAAnimation, finished flag: Bool) { + guard let animationID = animation.value(forKey: disappearanceAnimationIDKey) as? Int else { return } + defer { disappearingViewAnimations[animationID] = nil } + guard let view = disappearingViewAnimations[animationID]?.value else { return } + view.layer.removeAnimation(forKey: disappearanceAnimationKey) + guard !activeViews.contains(view) else { return } + view.removeFromSuperview() + } + + // MARK: - Private + + private func animateAppearanceIfNeeded(for view: UIView) { + guard UIView.inheritedAnimationDuration > 0 && !disableViewAppearanceAnimations else { return } + let animation = buildOpacityAnimation(initialOpacity: 0, targetOpacity: view.alpha) + view.layer.add(animation, forKey: appearanceAnimationKey) + } + + private func performDisappearanceWithAnimationIfNeeded(for view: UIView) { + guard UIView.inheritedAnimationDuration > 0 else { + view.removeFromSuperview() + return + } + let animation = buildOpacityAnimation(initialOpacity: view.alpha, targetOpacity: 0) + animation.delegate = self + animation.setValue(disappearanceAnimationID, forKey: disappearanceAnimationIDKey) + disappearingViewAnimations[disappearanceAnimationID] = Weak(value: view) + view.layer.add(animation, forKey: disappearanceAnimationKey) + disappearanceAnimationID += 1 + } + + private func buildOpacityAnimation(initialOpacity: CGFloat, targetOpacity: CGFloat) -> CABasicAnimation { + let animation = CABasicAnimation(keyPath: "opacity") + animation.duration = UIView.inheritedAnimationDuration + animation.fillMode = .forwards + animation.isRemovedOnCompletion = false + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + animation.fromValue = initialOpacity + animation.toValue = targetOpacity + return animation + } +} + +fileprivate extension UIView { + var animationKeys: Set { + return Set(layer.animationKeys() ?? []) + } + + func removeAnimations(for animationKeys: Set) { + animationKeys.forEach { layer.removeAnimation(forKey: $0) } + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/BodyState.swift b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/BodyState.swift new file mode 100644 index 0000000..79f7046 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/BodyState.swift @@ -0,0 +1,14 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@_exported import UIKit + +@MainActor +final class BodyState { + let bodyAppearanceCoordinator = BodyAppearanceCoordinator() + var activeSubviews: [UIView] = [] +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/HasBody.swift b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/HasBody.swift new file mode 100644 index 0000000..81a1ab6 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/HasBody.swift @@ -0,0 +1,74 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutCore +@_exported import UIKit + +@MainActor +public protocol HasBody { + @LayoutBuilder var body: Layout { get } +} + +extension HasBody where Self: UIView { + /// Returns the body size if isBodyEnabled is true, otherwise nil. + public func bodySizeThatFits(_ size: CGSize) -> CGSize? { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) + } +} + +@MainActor @objc(QLBodyCoordinationExperiments) +public class BodyCoordinationExperiments: NSObject { + @objc static public var preventUnusedCollectionViewCellSizing: Bool = true +} + +// MARK: - Public + +extension UIView { + + @objc(quick_bodyContainerView) /// Provide unique name for ObjC runtime to avoid method name collision. + open var bodyContainerView: UIView { + return self + } + + @objc(quick_isBodyEnabled) /// Provide unique name for ObjC runtime to avoid method name collision. + open var isBodyEnabled: Bool { + return true + } + + @objc(quick_isBodySizingEnabled) /// Provide unique name for ObjC runtime to avoid method name collision. + internal var isBodySizingEnabled: Bool { + return true + } + + @objc(quick_isCachingEnabled) /// Provide unique name for ObjC runtime to avoid method name collision. + open var isCachingEnabled: Bool { + return false + } + +} + +extension UICollectionViewCell { + override open var bodyContainerView: UIView { + return contentView + } + override var isBodySizingEnabled: Bool { + guard BodyCoordinationExperiments.preventUnusedCollectionViewCellSizing else { return true } + // Disable self sizing if the collection view layout will request sizing info unnecessarily. + // When the preferred attribute selector is overriden from the base class, we need to provide + // sizing info as this means the layout may need the sizing. UITableView does not have the same + // problem, this is specific to UICollectionView. + let preferredFittingSelector = #selector(UICollectionViewLayout.shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)) + guard let collectionView = superview as? UICollectionView else { return true } + return collectionView.collectionViewLayout.method(for: preferredFittingSelector) != UICollectionViewLayout.instanceMethod(for: preferredFittingSelector) + } +} + +extension UITableViewCell { + override open var bodyContainerView: UIView { + return contentView + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/_QuickLayoutViewImplementation.swift b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/_QuickLayoutViewImplementation.swift new file mode 100644 index 0000000..c49e147 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/ViewImplementation/_QuickLayoutViewImplementation.swift @@ -0,0 +1,154 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutCore +@_exported import UIKit + +@MainActor +struct ScopedLayoutCache { + static var bodyCache: [ObjectIdentifier: Layout]? + static var layoutCache: [ObjectIdentifier: LayoutNode]? + static var viewsWithCache: Set? + + private static weak var currentOwner: UIView? + + static func enter(_ view: UIView) { + if Self.currentOwner == nil { + Self.bodyCache = [:] + Self.layoutCache = [:] + Self.viewsWithCache = [] + Self.currentOwner = view + } + } + + static func leave(_ view: UIView) { + if Self.currentOwner === view { + Self.bodyCache = nil + Self.layoutCache = nil + Self.viewsWithCache = nil + Self.currentOwner = nil + } + } +} + +/** + _QuickLayoutViewImplementation provides a canonical implementation of QuickLayout integration + for a UIView with a Declarative interface. You shouldn't use this directly, but you may + inherit usage via the Declarative protocol or upcoming macro support. +*/ +@MainActor +public struct _QuickLayoutViewImplementation { + + // MARK: - Public + public static func willMove(_ view: UIView, toWindow newWindow: UIWindow?) { + guard view.isBodyEnabled else { return } + let storage = getStorage(view) + storage.bodyAppearanceCoordinator.coordinateMove(to: newWindow) + } + + public static func layoutSubviews(_ view: UIView & HasBody) { + guard view.isBodyEnabled else { + removeBodyIfNeeded(view) + return + } + + let cachingIsEnabled = view.isCachingEnabled + if cachingIsEnabled { + ScopedLayoutCache.enter(view) + } + + let body = getBody(view) + updateBodyIfNeeded(view, body) + let storage = getStorage(view) + storage.bodyAppearanceCoordinator.beginViewAppearanceUpdates() + let layoutDirection: LayoutDirection = view.effectiveUserInterfaceLayoutDirection == .rightToLeft ? .rightToLeft : .leftToRight + var cachedLayout: LayoutNode? + let cacheKey = ScopedLayoutCache.layoutCache != nil ? ObjectIdentifier(view) : nil + if let cacheKey, let layout = ScopedLayoutCache.layoutCache?[cacheKey], layout.size == view.bounds.size { + cachedLayout = layout + } + body._applyFrame(view.bounds, alignment: .center, layoutDirection: layoutDirection, cachedLayout: cachedLayout) + storage.bodyAppearanceCoordinator.commitViewAppearanceUpdates() + + if cachingIsEnabled { + if let viewsWithCache = ScopedLayoutCache.viewsWithCache { + for subview in viewsWithCache { + subview.layoutIfNeeded() + } + } + + ScopedLayoutCache.leave(view) + } + } + + public static func sizeThatFits(_ view: UIView & HasBody, size: CGSize) -> CGSize? { + guard view.isBodyEnabled, view.isBodySizingEnabled else { return nil } + let body = getBody(view) + updateBodyIfNeeded(view, body) + let layoutDirection: LayoutDirection = view.effectiveUserInterfaceLayoutDirection == .rightToLeft ? .rightToLeft : .leftToRight + let layout = body.layoutThatFits(size, layoutDirection: layoutDirection) + let cacheKey = ScopedLayoutCache.layoutCache != nil ? ObjectIdentifier(view) : nil + if let cacheKey { + ScopedLayoutCache.viewsWithCache?.insert(view) + ScopedLayoutCache.layoutCache?[cacheKey] = layout + } + return layout.size + } + + public static func quick_flexibility(_ view: UIView & HasBody, for axis: Axis) -> Flexibility? { + guard view.isBodyEnabled else { return nil } + return getBody(view).quick_flexibility(for: axis) + } + + // MARK: - Private + + private static var bodyStateKey: UInt8 = 0 + + private static func getStorage(_ view: UIView) -> BodyState { + guard let storage = objc_getAssociatedObject(view, &_QuickLayoutViewImplementation.bodyStateKey) as? BodyState else { + let newStorage = BodyState() + objc_setAssociatedObject(view, &_QuickLayoutViewImplementation.bodyStateKey, newStorage, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return newStorage + } + return storage + } + + private static func removeBodyIfNeeded(_ view: UIView) { + updateBodyIfNeeded(view, EmptyLayout()) + } + + private static func updateBodyIfNeeded(_ view: UIView, _ newBody: Layout) { + let newViews = newBody.views() + let storage = getStorage(view) + guard newViews != storage.activeSubviews else { return } + // We need a real diff to ensure correct view ordering + for operation in newViews.difference(from: storage.activeSubviews) { + switch operation { + case .insert(let index, let insertedView, _): + storage.bodyAppearanceCoordinator.coordinateAppearance(of: insertedView, in: view.bodyContainerView, index: index) + case .remove(_, let view, _): + storage.bodyAppearanceCoordinator.coordinateDisappearance(of: view) + } + } + storage.activeSubviews = newViews + } + + private static func getBody(_ view: UIView & HasBody) -> Layout { + let cacheKey = ScopedLayoutCache.bodyCache != nil ? ObjectIdentifier(view) : nil + if let cacheKey, let body = ScopedLayoutCache.bodyCache?[cacheKey] { + return body + } + let disableActions = CATransaction.disableActions() + CATransaction.setDisableActions(true) + let body = view.body + if let cacheKey { + ScopedLayoutCache.bodyCache?[cacheKey] = body + } + CATransaction.setDisableActions(disableActions) + return body + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/ViewProxy.swift b/Sources/QuickLayout/QuickLayoutBridge/ViewProxy.swift new file mode 100644 index 0000000..edecef9 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/ViewProxy.swift @@ -0,0 +1,122 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutCore +@_exported import UIKit + +// MARK: - Definition + +/** + ViewProxy should be utilized for sizing purposes. If you need to find the size of a layout, + but you don't have the views yet, you can build a matching layout using ViewProxy objects + in place of views. + + For regular layout, where views exist, ViewProxy should not be used. It is a stand in for + missing views only. + */ +public struct ViewProxy: LeafElement { + private let horizontalFlexibility: Flexibility + private let verticalFlexibility: Flexibility + private let proxySizingCalculation: (CGSize) -> CGSize + + public static func empty() -> ViewProxy { + return ViewProxy(width: 0, height: 0) + } + + public init(width: CGFloat, height: CGFloat) { + self.init(flexibility: .fixedSize) { _ in CGSize(width: width, height: height) } + } + + public init(width: CGFloat) { + self.init(horizontal: .fixedSize, vertical: .fullyFlexible) { proposedSize in + CGSize(width: width, height: proposedSize.height) + } + } + + public init(height: CGFloat) { + self.init(horizontal: .fullyFlexible, vertical: .fixedSize) { proposedSize in + CGSize(width: proposedSize.width, height: height) + } + } + + public init() { + self.init(horizontal: .fullyFlexible, vertical: .fullyFlexible) { proposedSize in + proposedSize + } + } + + public init(flexibility: Flexibility, proxySizingCalculation: @escaping (CGSize) -> CGSize) { + self.horizontalFlexibility = flexibility + self.verticalFlexibility = flexibility + self.proxySizingCalculation = proxySizingCalculation + } + + public init(horizontal horizontalFlexibility: Flexibility, vertical verticalFlexibility: Flexibility, proxySizingCalculation: @escaping (CGSize) -> CGSize) { + self.horizontalFlexibility = horizontalFlexibility + self.verticalFlexibility = verticalFlexibility + self.proxySizingCalculation = proxySizingCalculation + } + + // MARK: - Element Conformance + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> QuickLayoutCore.LayoutNode { + return LayoutNode(view: nil, dimensions: ElementDimensions(proxySizingCalculation(proposedSize))) + } + + public func quick_flexibility(for axis: QuickLayoutCore.Axis) -> QuickLayoutCore.Flexibility { + switch axis { + case .horizontal: return horizontalFlexibility + case .vertical: return verticalFlexibility + } + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + } + + public func backingView() -> UIView? { + nil + } +} + +// MARK: - UIKit Conformance + +public extension UILabel { + // While we could use NSString's boundingRectWithSize:, UILabel applies + // caching, performance optimizations, and includes details like shadow offset. + private static let sizingLabel = UILabel() + static func proxy(for text: String, numberOfLines: Int = 1, with font: UIFont? = nil) -> ViewProxy { + return ViewProxy(flexibility: .partial) { constrainingSize in + assert(Thread.isMainThread, "UILabel ViewProxy can be used only on the main thread. Prefer using FOALabel.swift if you need to have background safe advance sizing.") + sizingLabel.attributedText = nil + sizingLabel.text = text + sizingLabel.font = font + sizingLabel.numberOfLines = numberOfLines + return sizingLabel.quick_layoutThatFits(constrainingSize).size + } + } + static func proxy(for attributedText: NSAttributedString, numberOfLines: Int = 1) -> ViewProxy { + return ViewProxy(flexibility: .partial) { constrainingSize in + assert(Thread.isMainThread, "UILabel ViewProxy can be used only on the main thread. Prefer using FOALabel.swift if you need to have background safe advance sizing.") + sizingLabel.text = nil + sizingLabel.attributedText = attributedText + sizingLabel.numberOfLines = numberOfLines + return sizingLabel.quick_layoutThatFits(constrainingSize).size + } + } +} + +public extension UIImageView { + static func proxy(for image: UIImage?) -> ViewProxy { + return ViewProxy(flexibility: .fixedSize) { _ in + return image?.size ?? .zero + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/Weak.swift b/Sources/QuickLayout/QuickLayoutBridge/Weak.swift new file mode 100644 index 0000000..903e801 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/Weak.swift @@ -0,0 +1,10 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +struct Weak { + weak var value: T? +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AlignmentGuidesServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AlignmentGuidesServerSnapshotTests.swift new file mode 100644 index 0000000..525bb52 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AlignmentGuidesServerSnapshotTests.swift @@ -0,0 +1,422 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +// MARK: - Types + +extension VerticalAlignment { + private struct CustomVerticalAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[VerticalAlignment.bottom] + } + } + static let custom = VerticalAlignment( + CustomVerticalAlignment.self + ) +} + +// MARK: - Tests + +@MainActor +class AlignmentGuidesServerSnaspshotTests: FBServerSnapshotTestCase { + + func testAlignmentGuideTaskList() { + /// Expecting a task list with two categories, where + /// tasks in each category are inset by 10 points from the leading edge. + let groceriesTitleLabel = { + let label = UILabel() + label.text = "Groceries" + label.font = UIFont.systemFont(ofSize: 18, weight: .bold) + return label + }() + + let groceryItemLabels = ["Milk", "Eggs", "Bananas"].map { + let label = UILabel() + label.text = $0 + return label + } + + let tasksTitleLabel = { + let label = UILabel() + label.text = "Tasks" + label.font = UIFont.systemFont(ofSize: 18, weight: .bold) + return label + }() + + let taskItemLabels = ["Laundry", "Cook dinner"].map { + let label = UILabel() + label.text = $0 + return label + } + + let layout = VStack(alignment: .leading, spacing: 5) { + groceriesTitleLabel + for item in groceryItemLabels { + item.alignmentGuide(.leading) { _ in -10 } + } + Spacer(20) + tasksTitleLabel + for item in taskItemLabels { + item.alignmentGuide(.leading) { _ in -10 } + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)), + alignment: .center, + containerBackground: .white + ) + } + + func testInvalidAlignmentGuideBehavior() { + /// Expecting a task list with no insets. + + let groceryItemLabels = ["Milk", "Eggs", "Bananas"].map { + let label = UILabel() + label.text = $0 + return label + } + + let layout = VStack(alignment: .leading, spacing: 5) { + for (index, item) in groceryItemLabels.enumerated() { + item.alignmentGuide(.leading) { _ in + switch index { + case 0: .nan + case 1: .infinity + default: 0 + } + } + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)), + alignment: .center, + containerBackground: .white + ) + } + + func testNestedVStackAlignmentBehavior() { + let greenView = { + let view = UIView() + view.backgroundColor = .systemGreen + return view + }() + let blueView = { + let view = UIView() + view.backgroundColor = .systemBlue + return view + }() + let redView = { + let view = UIView() + view.backgroundColor = .systemRed + return view + }() + let layout = VStack(alignment: .leading) { + greenView + .frame(width: 50, height: 50) + HStack { + blueView + .frame(width: 50, height: 50) + .alignmentGuide(.leading) { _ in -10 } + } + redView + .frame(width: 50, height: 50) + .alignmentGuide(.leading) { _ in -10 } + } + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)), + alignment: .center, + containerBackground: .white + ) + } + + func testNestedZStackAlignmentBehavior() { + let greenView = { + let view = UIView() + view.backgroundColor = .systemGreen + return view + }() + let blueView = { + let view = UIView() + view.backgroundColor = .systemBlue + return view + }() + let redView = { + let view = UIView() + view.backgroundColor = .systemRed + return view + }() + let layout = ZStack(alignment: .topLeading) { + greenView + .frame(width: 70, height: 60) + HStack { + blueView + .frame(width: 50, height: 50) + .alignmentGuide(.leading) { _ in -10 } + } + redView + .frame(width: 50, height: 50) + .alignmentGuide(.leading) { _ in -10 } + } + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)), + alignment: .center, + containerBackground: .white + ) + } + func testAlignmentGuidesArePropagatedByLayoutsWithSingleChildElements() { + let greenView = { + let view = UIView() + view.backgroundColor = .systemGreen + return view + }() + let blueView = { + let view = UIView() + view.backgroundColor = .systemBlue + return view + }() + let redView = { + let view = UIView() + view.backgroundColor = .systemRed + return view + }() + let yellowView = { + let view = UIView() + view.backgroundColor = .systemYellow + return view + }() + let clearView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + let clearView2 = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + let layout = VStack(alignment: .leading) { + ZStack { + blueView + .alignmentGuide(.leading) { _ in -15 } + .frame(width: 50, height: 50) + .padding(8) + .aspectRatio(CGSize(width: 1, height: 1)) + + } + VStack { + redView + .alignmentGuide(.leading) { _ in -5 } + .frame(width: 50, height: 50) + .padding(8) + .offset(x: 0, y: 0) + } + HStack { + greenView + .alignmentGuide(.leading) { _ in 5 } + .frame(width: 50, height: 50) + .padding(8) + .aspectRatio(CGSize(width: 1, height: 1)) + .overlay { + clearView + } + } + HFlow { + yellowView + .alignmentGuide(.leading) { _ in 15 } + .frame(width: 50, height: 50) + .padding(8) + .aspectRatio(CGSize(width: 1, height: 1)) + .background { + clearView2 + } + } + } + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 500)), + alignment: .center, + containerBackground: .white + ) + } + + func testDefaultAlignmentGuidesAreNotPropagatedThroughMultipleChildContainers() { + let layout = buildAlignmentGuidePropagationLayout(with: .bottom) + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 500)), + alignment: .center, + containerBackground: .white + ) + } + + func testCustomAlignmentGuidesArePropagatedThroughMultipleChildContainers() { + let layout = buildAlignmentGuidePropagationLayout(with: .custom) + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 500)), + alignment: .center, + containerBackground: .white + ) + } + + func testCustomAlignmentGuidePropagationPassesCorrectDimensions() { + let iconView = UIImageView(image: UIImage(systemName: "face.smiling")!) // swiftlint:disable:this force_unwrapping + let titleView = UILabel() + let subtitleLabel = UILabel() + + titleView.text = "Mauris fringilla ligula felis, nec pharetra velit congue id. Aenean hendrerit arcu lorem, in tempor est posuere id." + titleView.font = UIFont.preferredFont(forTextStyle: .headline) + titleView.numberOfLines = 0 + + subtitleLabel.text = "Lorem ipsum dolor sit amet consectetur adipiscing elit" + subtitleLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) + subtitleLabel.numberOfLines = 0 + + iconView.layer.borderColor = UIColor.systemGray.cgColor + iconView.layer.borderWidth = 1 + + let layout = HStack(alignment: .custom) { + iconView + .resizable() + .frame(width: 20, height: 20) + .alignmentGuide(.custom, computeValue: { d in d[.top] + d.height / 2 }) + Spacer(16) + VStack(alignment: .leading) { + titleView + .alignmentGuide(.custom, computeValue: { d in d[.top] + d.height / 2 }) + subtitleLabel + } + Spacer() + } + .padding(16) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 500)), + alignment: .center, + containerBackground: .white + ) + } + + func testReferencingSameAlignmentIDWithinGuide() { + let titleView = UILabel() + titleView.text = "Title" + titleView.font = .systemFont(ofSize: 17, weight: .bold) + + let iconView = UIImageView(image: UIImage(systemName: "face.smiling")!) // swiftlint:disable:this force_unwrapping + + let layout = VStack(alignment: .leading) { + titleView + iconView + .alignmentGuide(.leading) { d in + d[.leading] + 10 + } + } + .padding(16) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 500)), + alignment: .center, + containerBackground: .white + ) + } + + func testCorrectDimensionsArePassedToAlignmentGuide() { + let titleView = UILabel() + titleView.text = "Right" + titleView.font = .systemFont(ofSize: 17) + + let titleView2 = UILabel() + titleView2.text = "Left" + titleView2.font = .systemFont(ofSize: 17) + + let layout = VStack(alignment: .leading) { + VStack(alignment: .leading) { + titleView + } + VStack(alignment: .leading) { + titleView2 + .alignmentGuide(.leading) { d in + d.width + } + .padding(.horizontal, 20) + } + } + .padding(16) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 500)), + alignment: .center, + containerBackground: .white + ) + } + + // MARK: - Private + + private func buildAlignmentGuidePropagationLayout(with alignment: VerticalAlignment) -> Layout { + struct Model { + let emojiString: String + let emojiTitle: String + let emojiDescription: String? + } + struct EmojiViews { + let emojiView: UIView + let emojiTitleView: UIView + let emojiDescriptionView: UIView? + } + let titleView = UILabel() + titleView.text = "Odd one out?" + titleView.font = .systemFont(ofSize: 17, weight: .bold) + let models: [Model] = [ + .init(emojiString: "🍔", emojiTitle: "Burger", emojiDescription: nil), + .init(emojiString: "🍇", emojiTitle: "Grape", emojiDescription: nil), + .init(emojiString: "🏡", emojiTitle: "House", emojiDescription: "This one!"), + .init(emojiString: "🍎", emojiTitle: "Apple", emojiDescription: nil), + ] + let emojiViews = models.map { + let emojiView = UILabel() + emojiView.text = $0.emojiString + let titleView = UILabel() + titleView.text = $0.emojiTitle + let descriptionView: UILabel? + if let description = $0.emojiDescription { + descriptionView = UILabel() + descriptionView?.text = description + descriptionView?.textColor = .secondaryLabel + descriptionView?.font = .systemFont(ofSize: 13) + emojiView.font = .systemFont(ofSize: 34) + } else { + descriptionView = nil + } + return EmojiViews(emojiView: emojiView, emojiTitleView: titleView, emojiDescriptionView: descriptionView) + } + return VStack(spacing: 30) { + titleView + HStack(alignment: alignment, spacing: 16) { + for emojiViewData in emojiViews { + VStack { + emojiViewData.emojiView + emojiViewData.emojiTitleView + .alignmentGuide(alignment) { _ in 0 } + emojiViewData.emojiDescriptionView + } + } + } + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ApplyFrameServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ApplyFrameServerSnapshotTests.swift new file mode 100644 index 0000000..e2c3f86 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ApplyFrameServerSnapshotTests.swift @@ -0,0 +1,71 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class ApplyFramyServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTestWith(alignment: Alignment?) { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let layout = HStack { + view1 + .frame(width: 100, height: 100) + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)), + alignment: alignment, + containerBackground: ColorPallete.blue + ) + } + + func testDefaultAlignment() { + runTestWith(alignment: nil) + } + + func testCenterAlignment() { + runTestWith(alignment: .center) + } + + func testTopLeadingAlignment() { + runTestWith(alignment: .topLeading) + } + + func testTopAlignment() { + runTestWith(alignment: .top) + } + + func testTopTrailingAlignment() { + runTestWith(alignment: .topTrailing) + } + + func testLeadingAlignment() { + runTestWith(alignment: .leading) + } + + func testTrailingAlignment() { + runTestWith(alignment: .trailing) + } + + func testBottomTrailingAlignment() { + runTestWith(alignment: .bottomTrailing) + } + + func testBottomLeadingAlignment() { + runTestWith(alignment: .bottomLeading) + } + + func testBottomAlignment() { + runTestWith(alignment: .bottom) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioServerSnapshotTests.swift new file mode 100644 index 0000000..a9bcd52 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioServerSnapshotTests.swift @@ -0,0 +1,35 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import QuickLayoutBridge +import XCTest + +@MainActor +class AspectRatioServerSnapshotTests: FBServerSnapshotTestCase { + + func testAspectRatioWithTextField() { + let textField = UITextField() + textField.backgroundColor = ColorPallete.red + + let view = UIView() + view.backgroundColor = ColorPallete.blue + + let layout = HStack { + textField + Spacer(8) + view + .resizable() + .aspectRatio(CGSize(width: 3, height: 2)) + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 44)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithFiniteSizeServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithFiniteSizeServerSnapshotTests.swift new file mode 100644 index 0000000..459245d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithFiniteSizeServerSnapshotTests.swift @@ -0,0 +1,119 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class AspectRatioWithFiniteSizeServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTestWith(aspectRatio: CGSize, contentMode: ContentMode, proposedSize: CGSize) { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.layer.borderColor = UIColor.black.cgColor + view2.layer.borderWidth = 1 + + let layout = ZStack { + view1 + .aspectRatio(aspectRatio, contentMode: contentMode) + view2 + } + .frame(width: proposedSize.width, height: proposedSize.height) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 200, height: 200)) + ) + } + + // --- FILL + + func testAspectRatio_fill_1_1_50_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fill, proposedSize: CGSize(width: 50, height: 50)) + } + + func testAspectRatio_fill_1_1_50_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fill, proposedSize: CGSize(width: 50, height: 100)) + } + + func testAspectRatio_fill_1_1_100_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fill, proposedSize: CGSize(width: 100, height: 50)) + } + + // --- + + func testAspectRatio_fill_1_2_50_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fill, proposedSize: CGSize(width: 50, height: 50)) + } + + func testAspectRatio_fill_1_2_50_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fill, proposedSize: CGSize(width: 50, height: 100)) + } + + func testAspectRatio_fill_1_2_100_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fill, proposedSize: CGSize(width: 100, height: 50)) + } + + // --- + + func testAspectRatio_fill_2_1_50_50() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fill, proposedSize: CGSize(width: 50, height: 50)) + } + + func testAspectRatio_fill_2_1_50_100() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fill, proposedSize: CGSize(width: 50, height: 100)) + } + + func testAspectRatio_fill_2_1_100_50() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fill, proposedSize: CGSize(width: 100, height: 50)) + } + + // --- FIT + + func testAspectRatio_fit_1_1_50_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fit, proposedSize: CGSize(width: 50, height: 50)) + } + + func testAspectRatio_fit_1_1_50_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fit, proposedSize: CGSize(width: 50, height: 100)) + } + + func testAspectRatio_fit_1_1_100_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fit, proposedSize: CGSize(width: 100, height: 50)) + } + + // --- + + func testAspectRatio_fit_1_2_50_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fit, proposedSize: CGSize(width: 50, height: 50)) + } + + func testAspectRatio_fit_1_2_50_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fit, proposedSize: CGSize(width: 50, height: 100)) + } + + func testAspectRatio_fit_1_2_100_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fit, proposedSize: CGSize(width: 100, height: 50)) + } + + // --- + + func testAspectRatio_fit_2_1_50_50() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fit, proposedSize: CGSize(width: 50, height: 50)) + } + + func testAspectRatio_fit_2_1_50_100() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fit, proposedSize: CGSize(width: 50, height: 100)) + } + + func testAspectRatio_fit_2_1_100_50() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fit, proposedSize: CGSize(width: 100, height: 50)) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithInfiniteProposedHeightServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithInfiniteProposedHeightServerSnapshotTests.swift new file mode 100644 index 0000000..15af58d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithInfiniteProposedHeightServerSnapshotTests.swift @@ -0,0 +1,105 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class AspectRatioWithInfiniteProposedHeightServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTestWith(aspectRatio: CGSize, contentMode: ContentMode, proposedSize: CGSize) { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.layer.borderColor = UIColor.black.cgColor + view2.layer.borderWidth = 1 + view2.backgroundColor = .clear + + let layout = ZStack(alignment: .top) { + view1 + .aspectRatio(aspectRatio, contentMode: contentMode) + view2 + .frame(height: 200) + } + + let targetView = UIView() + targetView.addSubview(view1) + targetView.addSubview(view2) + targetView.frame.size = CGSize(width: proposedSize.width, height: 200) + + layout.applyFrame(CGRect(origin: .zero, size: proposedSize)) + + let backgroundView = UIView() + backgroundView.frame.size = CGSize(width: proposedSize.width, height: 200) + backgroundView.backgroundColor = .clear + backgroundView.addSubview(targetView) + targetView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] + FBTakeSnapshotOfViewAfterScreenUpdates(backgroundView, nil) + } + + // --- FILL + + func testAspectRatio_fill_1_1_50_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fill, proposedSize: CGSize(width: 50, height: CGFloat.infinity)) + } + + func testAspectRatio_fill_1_1_100_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fill, proposedSize: CGSize(width: 100, height: CGFloat.infinity)) + } + + // --- + + func testAspectRatio_fill_1_2_50_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fill, proposedSize: CGSize(width: 50, height: CGFloat.infinity)) + } + + func testAspectRatio_fill_1_2_100_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fill, proposedSize: CGSize(width: 100, height: CGFloat.infinity)) + } + + // --- + + func testAspectRatio_fill_2_1_50_inf() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fill, proposedSize: CGSize(width: 50, height: CGFloat.infinity)) + } + + func testAspectRatio_fill_2_1_100_inf() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fill, proposedSize: CGSize(width: 100, height: CGFloat.infinity)) + } + + // --- FIT + + func testAspectRatio_fit_1_1_50_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fit, proposedSize: CGSize(width: 50, height: CGFloat.infinity)) + } + + func testAspectRatio_fit_1_1_100_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fit, proposedSize: CGSize(width: 100, height: CGFloat.infinity)) + } + + // --- + + func testAspectRatio_fit_1_2_50_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fit, proposedSize: CGSize(width: 50, height: CGFloat.infinity)) + } + + func testAspectRatio_fit_1_2_100_inf() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fit, proposedSize: CGSize(width: 100, height: CGFloat.infinity)) + } + + // --- + + func testAspectRatio_fit_2_1_50_inf() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fit, proposedSize: CGSize(width: 50, height: CGFloat.infinity)) + } + + func testAspectRatio_fit_2_1_100_inf() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fit, proposedSize: CGSize(width: 100, height: CGFloat.infinity)) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithInfiniteProposedWidthServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithInfiniteProposedWidthServerSnapshotTests.swift new file mode 100644 index 0000000..01c42eb --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/AspectRatioWithInfiniteProposedWidthServerSnapshotTests.swift @@ -0,0 +1,105 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class AspectRatioWithInfiniteProposedWidthServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTestWith(aspectRatio: CGSize, contentMode: ContentMode, proposedSize: CGSize) { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.layer.borderColor = UIColor.black.cgColor + view2.layer.borderWidth = 1 + view2.backgroundColor = .clear + + let layout = ZStack(alignment: .leading) { + view1 + .aspectRatio(aspectRatio, contentMode: contentMode) + view2 + .frame(width: 200) + } + + let targetView = UIView() + targetView.addSubview(view1) + targetView.addSubview(view2) + targetView.frame.size = CGSize(width: 200, height: 200) + + layout.applyFrame(CGRect(origin: .zero, size: proposedSize)) + + let backgroundView = UIView() + backgroundView.frame.size = CGSize(width: 200, height: proposedSize.height) + backgroundView.backgroundColor = .clear + backgroundView.addSubview(targetView) + targetView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] + FBTakeSnapshotOfViewAfterScreenUpdates(backgroundView, nil) + } + + // --- FILL + + func testAspectRatio_fill_1_1_50_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fill, proposedSize: CGSize(width: CGFloat.infinity, height: 50)) + } + + func testAspectRatio_fill_1_1_inf_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fill, proposedSize: CGSize(width: CGFloat.infinity, height: 100)) + } + + // --- + + func testAspectRatio_fill_1_2_inf_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fill, proposedSize: CGSize(width: CGFloat.infinity, height: 50)) + } + + func testAspectRatio_fill_1_2_inf_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fill, proposedSize: CGSize(width: CGFloat.infinity, height: 100)) + } + + // --- + + func testAspectRatio_fill_2_1_inf_50() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fill, proposedSize: CGSize(width: CGFloat.infinity, height: 50)) + } + + func testAspectRatio_fill_2_1_inf_100() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fill, proposedSize: CGSize(width: CGFloat.infinity, height: 100)) + } + + // --- FIT + + func testAspectRatio_fit_1_1_inf_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fit, proposedSize: CGSize(width: CGFloat.infinity, height: 50)) + } + + func testAspectRatio_fit_1_1_inf_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 1), contentMode: .fit, proposedSize: CGSize(width: CGFloat.infinity, height: 100)) + } + + // --- + + func testAspectRatio_fit_1_2_inf_50() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fit, proposedSize: CGSize(width: CGFloat.infinity, height: 50)) + } + + func testAspectRatio_fit_1_2_inf_100() { + runTestWith(aspectRatio: CGSize(width: 1, height: 2), contentMode: .fit, proposedSize: CGSize(width: CGFloat.infinity, height: 100)) + } + + // --- + + func testAspectRatio_fit_2_1_inf_50() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fit, proposedSize: CGSize(width: CGFloat.infinity, height: 50)) + } + + func testAspectRatio_fit_2_1_inf_100() { + runTestWith(aspectRatio: CGSize(width: 2, height: 1), contentMode: .fit, proposedSize: CGSize(width: CGFloat.infinity, height: 100)) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/CustomAlignmentServerSnaspshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/CustomAlignmentServerSnaspshotTests.swift new file mode 100644 index 0000000..60a4e1f --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/CustomAlignmentServerSnaspshotTests.swift @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +private struct FirstThirdAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height / 3 + } +} + +@MainActor +class CustomAlignmentServerSnaspshotTests: FBServerSnapshotTestCase { + + func testFirstThirdAlignment() { + /// Expecting a 3x3 grid of blue rectangles. + /// Rectangles having different heights, though they are aligned by the first third of their height. + let colorViews = (1...9).map { _ in + let view = UIView() + view.backgroundColor = ColorPallete.blue + return view + } + + let layout = HStack(alignment: VerticalAlignment(FirstThirdAlignment.self), spacing: 2) { + VStack(spacing: 2) { + colorViews[0] + colorViews[1] + colorViews[2] + }.frame(height: 140) + VStack(spacing: 2) { + colorViews[3] + colorViews[4] + colorViews[5] + }.frame(height: 250) + VStack(spacing: 2) { + colorViews[6] + colorViews[7] + colorViews[8] + }.frame(height: 180) + } + .padding(20) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)), + alignment: .center, + containerBackground: .white + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/EmptyLayoutServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/EmptyLayoutServerSnapshotTests.swift new file mode 100644 index 0000000..52b56df --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/EmptyLayoutServerSnapshotTests.swift @@ -0,0 +1,58 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import QuickLayoutBridge +import XCTest + +@MainActor +class EmptyLayoutServerSnapshotTests: FBServerSnapshotTestCase { + + func testEmptyLayoutWhenUsedInStack() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let layout = HStack { + + view1 + .resizable() + .frame(width: 50, height: 50) + + // EmptyLayouts shouldn't affect the positioning of the views + EmptyLayout() + EmptyLayout() + EmptyLayout() + EmptyLayout() + EmptyLayout() + + view2 + .resizable() + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 50)) + ) + } + + func testEmptyLayoutWhenUsedAsLayout() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + takeSnapshot( + with: EmptyLayout(), + in: .exact(CGSize(width: 300, height: 50)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ExpandeByServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ExpandeByServerSnapshotTests.swift new file mode 100644 index 0000000..261a909 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ExpandeByServerSnapshotTests.swift @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import QuickLayoutBridge +import XCTest + +@MainActor +class ExpandByServerSnapshotTests: FBServerSnapshotTestCase { + + func testAspectRatioWithTextField() { + + let size = 20 + + let firstView = ViewWithSize(customSize: CGSize(width: size, height: size)) + firstView.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view2.backgroundColor = ColorPallete.blue + + let view3 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view3.backgroundColor = ColorPallete.blue + + let view4 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view4.backgroundColor = ColorPallete.blue + + let view5 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view5.backgroundColor = ColorPallete.blue + + let view6 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view6.backgroundColor = ColorPallete.blue + + let view7 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view7.backgroundColor = ColorPallete.blue + + let lastView = ViewWithSize(customSize: CGSize(width: size, height: size)) + lastView.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + firstView + view2 + .expand(by: CGSize(width: size, height: size)) + view3 + .expand(by: CGSize(width: size, height: 0)) + view4 + .expand(by: CGSize(width: 0, height: size)) + view5 + .expand(by: CGSize(width: -100, height: 0)) + view6 + .expand(by: CGSize(width: CGFloat.nan, height: .nan)) // Invalid + view7 + .expand(by: CGSize(width: CGFloat.infinity, height: .infinity)) // Invalid + lastView + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/FlexibleFrameServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/FlexibleFrameServerSnapshotTests.swift new file mode 100644 index 0000000..e6c7faf --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/FlexibleFrameServerSnapshotTests.swift @@ -0,0 +1,738 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class FlexibleFrameServerSnaposhTests: FBServerSnapshotTestCase { + + func testFrameWithoutConstraints() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + + let layout = VStack(spacing: 10) { + view1 + .frame(width: 50) + .frame(minWidth: nil, maxWidth: nil) + .overlay { borderView1 } + .frame(width: 150) + .frame(height: 20) + + view2 + .frame(width: 100) + .frame(minWidth: nil, maxWidth: nil) + .overlay { borderView2 } + .frame(width: 150) + .frame(height: 20) + + view3 + .frame(width: 200) + .frame(minWidth: nil, maxWidth: nil) + .overlay { borderView3 } + .frame(width: 150) + .frame(height: 20) + + view4 + .frame(width: 250) + .frame(minWidth: nil, maxWidth: nil) + .overlay { borderView4 } + .frame(width: 150) + .frame(height: 20) + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameWithBothConstraints() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "5") + let view6 = ColorView(ColorPallete.yellow, text: "6") + let view7 = ColorView(ColorPallete.yellow, text: "7") + let view8 = ColorView(ColorPallete.yellow, text: "8") + let view9 = ColorView(ColorPallete.yellow, text: "9") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + let borderView5 = BorderView() + let borderView6 = BorderView() + let borderView7 = BorderView() + let borderView8 = BorderView() + let borderView9 = BorderView() + + let layout = VStack(alignment: .center, spacing: 10) { + view1 + .frame(width: 40) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView1 } + .frame(width: 50) + .frame(height: 20) + + view2 + .frame(width: 40) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView2 } + .frame(width: 150) + .frame(height: 20) + + view3 + .frame(width: 40) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView3 } + .frame(width: 250) + .frame(height: 20) + + view4 + .frame(width: 160) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView4 } + .frame(width: 50) + .frame(height: 20) + + view5 + .frame(width: 160) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView5 } + .frame(width: 150) + .frame(height: 20) + + view6 + .frame(width: 160) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView6 } + .frame(width: 250) + .frame(height: 20) + + view7 + .frame(width: 260) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView7 } + .frame(width: 50) + .frame(height: 20) + + view8 + .frame(width: 260) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView8 } + .frame(width: 150) + .frame(height: 20) + + view9 + .frame(width: 260) + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView9 } + .frame(width: 250) + .frame(height: 20) + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameWithBothConstraintsAndFlexibleChild() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "5") + let view6 = ColorView(ColorPallete.yellow, text: "6") + let view7 = ColorView(ColorPallete.yellow, text: "7") + let view8 = ColorView(ColorPallete.yellow, text: "8") + let view9 = ColorView(ColorPallete.yellow, text: "9") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + let borderView5 = BorderView() + let borderView6 = BorderView() + let borderView7 = BorderView() + let borderView8 = BorderView() + let borderView9 = BorderView() + + let layout = VStack(alignment: .center, spacing: 10) { + view1 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView1 } + .frame(width: 50) + .frame(height: 20) + + view2 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView2 } + .frame(width: 150) + .frame(height: 20) + + view3 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView3 } + .frame(width: 250) + .frame(height: 20) + + view4 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView4 } + .frame(width: 50) + .frame(height: 20) + + view5 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView5 } + .frame(width: 150) + .frame(height: 20) + + view6 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView6 } + .frame(width: 250) + .frame(height: 20) + + view7 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView7 } + .frame(width: 50) + .frame(height: 20) + + view8 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView8 } + .frame(width: 150) + .frame(height: 20) + + view9 + .frame(minWidth: 100, maxWidth: 200) + .overlay { borderView9 } + .frame(width: 250) + .frame(height: 20) + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameWithMinOnly() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "5") + let view6 = ColorView(ColorPallete.yellow, text: "6") + let view7 = ColorView(ColorPallete.yellow, text: "7") + let view8 = ColorView(ColorPallete.yellow, text: "8") + let view9 = ColorView(ColorPallete.yellow, text: "9") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + let borderView5 = BorderView() + let borderView6 = BorderView() + let borderView7 = BorderView() + let borderView8 = BorderView() + let borderView9 = BorderView() + + let layout = VStack(alignment: .center, spacing: 10) { + view1 + .frame(width: 40) + .frame(minWidth: 100) + .overlay { borderView1 } + .frame(width: 50) + .frame(height: 20) + + view2 + .frame(width: 40) + .frame(minWidth: 100) + .overlay { borderView2 } + .frame(width: 150) + .frame(height: 20) + + view3 + .frame(width: 40) + .frame(minWidth: 100) + .overlay { borderView3 } + .frame(width: 250) + .frame(height: 20) + + view4 + .frame(width: 160) + .frame(minWidth: 100) + .overlay { borderView4 } + .frame(width: 50) + .frame(height: 20) + + view5 + .frame(width: 160) + .frame(minWidth: 100) + .overlay { borderView5 } + .frame(width: 150) + .frame(height: 20) + + view6 + .frame(width: 160) + .frame(minWidth: 100) + .overlay { borderView6 } + .frame(width: 250) + .frame(height: 20) + + view7 + .frame(width: 260) + .frame(minWidth: 100) + .overlay { borderView7 } + .frame(width: 50) + .frame(height: 20) + + view8 + .frame(width: 260) + .frame(minWidth: 100) + .overlay { borderView8 } + .frame(width: 150) + .frame(height: 20) + + view9 + .frame(width: 260) + .frame(minWidth: 100) + .overlay { borderView9 } + .frame(width: 250) + .frame(height: 20) + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameWithMinOnlyFullyFlexibleChild() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "5") + let view6 = ColorView(ColorPallete.yellow, text: "6") + let view7 = ColorView(ColorPallete.yellow, text: "7") + let view8 = ColorView(ColorPallete.yellow, text: "8") + let view9 = ColorView(ColorPallete.yellow, text: "9") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + let borderView5 = BorderView() + let borderView6 = BorderView() + let borderView7 = BorderView() + let borderView8 = BorderView() + let borderView9 = BorderView() + + let layout = VStack(alignment: .center, spacing: 10) { + view1 + .frame(minWidth: 100) + .overlay { borderView1 } + .frame(width: 50) + .frame(height: 20) + + view2 + .frame(minWidth: 100) + .overlay { borderView2 } + .frame(width: 150) + .frame(height: 20) + + view3 + .frame(minWidth: 100) + .overlay { borderView3 } + .frame(width: 250) + .frame(height: 20) + + view4 + .frame(minWidth: 100) + .overlay { borderView4 } + .frame(width: 50) + .frame(height: 20) + + view5 + .frame(minWidth: 100) + .overlay { borderView5 } + .frame(width: 150) + .frame(height: 20) + + view6 + .frame(minWidth: 100) + .overlay { borderView6 } + .frame(width: 250) + .frame(height: 20) + + view7 + .frame(minWidth: 100) + .overlay { borderView7 } + .frame(width: 50) + .frame(height: 20) + + view8 + .frame(minWidth: 100) + .overlay { borderView8 } + .frame(width: 150) + .frame(height: 20) + + view9 + .frame(minWidth: 100) + .overlay { borderView9 } + .frame(width: 250) + .frame(height: 20) + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameWithMaxOnly() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "5") + let view6 = ColorView(ColorPallete.yellow, text: "6") + let view7 = ColorView(ColorPallete.yellow, text: "7") + let view8 = ColorView(ColorPallete.yellow, text: "8") + let view9 = ColorView(ColorPallete.yellow, text: "9") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + let borderView5 = BorderView() + let borderView6 = BorderView() + let borderView7 = BorderView() + let borderView8 = BorderView() + let borderView9 = BorderView() + + let layout = VStack(alignment: .center, spacing: 10) { + view1 + .frame(width: 40) + .frame(maxWidth: 200) + .overlay { borderView1 } + .frame(width: 50) + .frame(height: 20) + + view2 + .frame(width: 40) + .frame(maxWidth: 200) + .overlay { borderView2 } + .frame(width: 150) + .frame(height: 20) + + view3 + .frame(width: 40) + .frame(maxWidth: 200) + .overlay { borderView3 } + .frame(width: 250) + .frame(height: 20) + + view4 + .frame(width: 160) + .frame(maxWidth: 200) + .overlay { borderView4 } + .frame(width: 50) + .frame(height: 20) + + view5 + .frame(width: 160) + .frame(maxWidth: 200) + .overlay { borderView5 } + .frame(width: 150) + .frame(height: 20) + + view6 + .frame(width: 160) + .frame(maxWidth: 200) + .overlay { borderView6 } + .frame(width: 250) + .frame(height: 20) + + view7 + .frame(width: 260) + .frame(maxWidth: 200) + .overlay { borderView7 } + .frame(width: 50) + .frame(height: 20) + + view8 + .frame(width: 260) + .frame(maxWidth: 200) + .overlay { borderView8 } + .frame(width: 150) + .frame(height: 20) + + view9 + .frame(width: 260) + .frame(maxWidth: 200) + .overlay { borderView9 } + .frame(width: 250) + .frame(height: 20) + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameWithMaxOnlyFullyFlexibleChild() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "5") + let view6 = ColorView(ColorPallete.yellow, text: "6") + let view7 = ColorView(ColorPallete.yellow, text: "7") + let view8 = ColorView(ColorPallete.yellow, text: "8") + let view9 = ColorView(ColorPallete.yellow, text: "9") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + let borderView5 = BorderView() + let borderView6 = BorderView() + let borderView7 = BorderView() + let borderView8 = BorderView() + let borderView9 = BorderView() + + let layout = VStack(alignment: .center, spacing: 10) { + view1 + .frame(maxWidth: 200) + .overlay { borderView1 } + .frame(width: 50) + .frame(height: 20) + + view2 + .frame(maxWidth: 200) + .overlay { borderView2 } + .frame(width: 150) + .frame(height: 20) + + view3 + .frame(maxWidth: 200) + .overlay { borderView3 } + .frame(width: 250) + .frame(height: 20) + + view4 + .frame(maxWidth: 200) + .overlay { borderView4 } + .frame(width: 50) + .frame(height: 20) + + view5 + .frame(maxWidth: 200) + .overlay { borderView5 } + .frame(width: 150) + .frame(height: 20) + + view6 + .frame(maxWidth: 200) + .overlay { borderView6 } + .frame(width: 250) + .frame(height: 20) + + view7 + .frame(maxWidth: 200) + .overlay { borderView7 } + .frame(width: 50) + .frame(height: 20) + + view8 + .frame(maxWidth: 200) + .overlay { borderView8 } + .frame(width: 150) + .frame(height: 20) + + view9 + .frame(maxWidth: 200) + .overlay { borderView9 } + .frame(width: 250) + .frame(height: 20) + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameAlignment() { + let view1 = ColorView(ColorPallete.yellow, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "5") + let view6 = ColorView(ColorPallete.yellow, text: "6") + let view7 = ColorView(ColorPallete.yellow, text: "7") + let view8 = ColorView(ColorPallete.yellow, text: "8") + let view9 = ColorView(ColorPallete.yellow, text: "9") + let view10 = ColorView(ColorPallete.yellow, text: "10") + let view11 = ColorView(ColorPallete.yellow, text: "11") + let view12 = ColorView(ColorPallete.yellow, text: "12") + + let borderView1 = BorderView() + let borderView2 = BorderView() + let borderView3 = BorderView() + let borderView4 = BorderView() + let borderView5 = BorderView() + let borderView6 = BorderView() + let borderView7 = BorderView() + let borderView8 = BorderView() + let borderView9 = BorderView() + let borderView10 = BorderView() + let borderView11 = BorderView() + let borderView12 = BorderView() + + let borderView1_1 = BorderView() + let borderView1_2 = BorderView() + let borderView1_3 = BorderView() + let borderView1_4 = BorderView() + let borderView1_5 = BorderView() + let borderView1_6 = BorderView() + let borderView1_7 = BorderView() + let borderView1_8 = BorderView() + let borderView1_9 = BorderView() + let borderView1_10 = BorderView() + let borderView1_11 = BorderView() + let borderView1_12 = BorderView() + + let layout = VStack(spacing: 10) { + view1 + .frame(width: 150) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .center) + .overlay { borderView1 } + .frame(width: 50) + .frame(height: 20) + .overlay { borderView1_1 } + + view2 + .frame(width: 150) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .overlay { borderView2 } + .frame(width: 50) + .frame(height: 20) + .overlay { borderView1_2 } + + view3 + .frame(width: 150) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .trailing) + .overlay { borderView3 } + .frame(width: 50) + .frame(height: 20) + .overlay { borderView1_3 } + + view4 + .frame(width: 150) + .frame(minWidth: 0, alignment: .center) + .overlay { borderView4 } + .frame(width: 50) + .frame(height: 20) + .overlay { borderView1_4 } + + view5 + .frame(width: 150) + .frame(minWidth: 0, alignment: .leading) + .overlay { borderView5 } + .frame(width: 50) + .frame(height: 20) + .overlay { borderView1_5 } + + view6 + .frame(width: 150) + .frame(minWidth: 0, alignment: .trailing) + .overlay { borderView6 } + .frame(width: 200) + .frame(height: 20) + .overlay { borderView1_6 } + + view7 + .frame(width: 150) + .frame(minWidth: 0, alignment: .center) + .overlay { borderView7 } + .frame(width: 50) + .frame(height: 20) + .overlay { borderView1_7 } + + view8 + .frame(width: 150) + .frame(minWidth: 0, alignment: .leading) + .overlay { borderView8 } + .frame(width: 200) + .frame(height: 20) + .overlay { borderView1_8 } + + view9 + .frame(width: 150) + .frame(minWidth: 0, alignment: .trailing) + .overlay { borderView9 } + .frame(width: 200) + .frame(height: 20) + .overlay { borderView1_9 } + + view10 + .frame(width: 150) + .frame(maxWidth: .infinity, alignment: .center) + .overlay { borderView10 } + .frame(width: 50) + .frame(height: 20) + .overlay { borderView1_10 } + + view11 + .frame(width: 150) + .frame(maxWidth: .infinity, alignment: .leading) + .overlay { borderView11 } + .frame(width: 200) + .frame(height: 20) + .overlay { borderView1_11 } + + view12 + .frame(width: 150) + .frame(maxWidth: .infinity, alignment: .trailing) + .overlay { borderView12 } + .frame(width: 200) + .frame(height: 20) + .overlay { borderView1_12 } + } + .frame(width: 320) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ForEachServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ForEachServerSnapshotTests.swift new file mode 100644 index 0000000..0618ce1 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ForEachServerSnapshotTests.swift @@ -0,0 +1,142 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class ForEachServerSnaposhTests: FBServerSnapshotTestCase { + + func testForEach() { + + let size = 20 + + let view1 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view2.backgroundColor = ColorPallete.blue + + let view3 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 20) { + ForEach([view1, view2, view3]) + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)) + ) + } + + func testForEachWithViewBlock() { + + let size = 20 + + let view1 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view2.backgroundColor = ColorPallete.blue + + let view3 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 20) { + view1 + ForEach([view2, view3]) { view in + view.resizable().frame(width: 30, height: 30) + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)) + ) + } + + func testForEachWithViewElementBlock() { + + let size = 20 + + let view1 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view2.backgroundColor = ColorPallete.blue + + let view3 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 20) { + view1 + ForEach([view2.padding(.top, 8), view3.padding(.top, 8)]) { element in + element.offset(y: -16) + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)) + ) + } + + func testForLoop() { + + let size = 20 + + let view1 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view2.backgroundColor = ColorPallete.blue + + let view3 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 20) { + for view in [view1, view2, view3] { + view + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)) + ) + } + + func testForLoopWithModifiers() { + + let size = 20 + + let view1 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view2.backgroundColor = ColorPallete.blue + + let view3 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 20) { + view1 + for view in [view2, view3] { + view + .resizable() + .frame(width: 30, height: 30) + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/FrameServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/FrameServerSnapshotTests.swift new file mode 100644 index 0000000..709cbdc --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/FrameServerSnapshotTests.swift @@ -0,0 +1,120 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class FrameServerSnaposhTests: FBServerSnapshotTestCase { + + func testFrameAlignment() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + let view2 = UIImageView(image: generateTestImage(with: "2", size: size)) + let view3 = UIImageView(image: generateTestImage(with: "3", size: size)) + let view4 = UIImageView(image: generateTestImage(with: "4", size: size)) + let view5 = UIImageView(image: generateTestImage(with: "5", size: size)) + let view6 = UIImageView(image: generateTestImage(with: "6", size: size)) + let view7 = UIImageView(image: generateTestImage(with: "7", size: size)) + let view8 = UIImageView(image: generateTestImage(with: "8", size: size)) + let view9 = UIImageView(image: generateTestImage(with: "9", size: size)) + + let frameSize = CGSize(width: size.width * 3, height: size.height * 3) + let layout = ZStack { + view1 + .frame(width: frameSize.width, height: frameSize.height, alignment: .topLeading) + view2 + .frame(width: frameSize.width, height: frameSize.height, alignment: .top) + view3 + .frame(width: frameSize.width, height: frameSize.height, alignment: .topTrailing) + view4 + .frame(width: frameSize.width, height: frameSize.height, alignment: .leading) + view5 + .frame(width: frameSize.width, height: frameSize.height, alignment: .center) + view6 + .frame(width: frameSize.width, height: frameSize.height, alignment: .trailing) + view7 + .frame(width: frameSize.width, height: frameSize.height, alignment: .bottomLeading) + view8 + .frame(width: frameSize.width, height: frameSize.height, alignment: .bottom) + view9 + .frame(width: frameSize.width, height: frameSize.height, alignment: .bottomTrailing) + } + + takeSnapshot( + with: layout, + in: .proposed(frameSize) + ) + } + + func testFrameTruncatesLabel() { + let view1 = UILabel() + view1.text = "This is a very long label" + + let view2 = UILabel() + view2.text = "This is a very long label" + + let layout = VStack(alignment: .leading) { + view1 + view2 + .frame(width: 100) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testFrameLimitsUIViewSize() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let layout = VStack(alignment: .leading) { + view1 + .frame(width: 100, height: 100) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testFrameLimitsUIViewSizeHorizontally() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let layout = VStack(alignment: .leading) { + view1 + .frame(width: 100) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testFrameLimitsUIViewSizeVertically() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let layout = VStack(alignment: .leading) { + view1 + .frame(height: 100) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/GridLayoutServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/GridLayoutServerSnapshotTests.swift new file mode 100644 index 0000000..99cd310 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/GridLayoutServerSnapshotTests.swift @@ -0,0 +1,1431 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +final class GridLayoutServerSnapshotTests: FBServerSnapshotTestCase { + + // MARK: - Test builder functions + + // Single child cases + func buildSingleChildSingleRow() { + let view = ColorView(ColorPallete.red, text: "1") + + let layout = Grid { + GridRow { + view + .frame(width: 100, height: 100) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildSingleChildSingleRowUnbounded() { + let view = ColorView(ColorPallete.red, text: "1") + + let layout = Grid { + GridRow { + view + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildSingleChildMultipleRowUnbounded() { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.red, text: "3") + + let layout = Grid { + GridRow { + view1 + } + GridRow { + view2 + } + GridRow { + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + // Single Child Multiple Row cases + + func buildSingleChildMultipleRow(verticalSpacing: CGFloat = 0) { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let layout = Grid(verticalSpacing: verticalSpacing) { + GridRow { + view1 + .frame(width: 50, height: 50) + } + GridRow { + view2 + .frame(width: 50, height: 50) + } + GridRow { + view3 + .frame(width: 50, height: 50) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + // Multiple children single row cases + + func buildMultipleChildrenSingleRow(horizontalSpacing: CGFloat = 0, layoutDirection: LayoutDirection = .leftToRight) { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let layout = Grid(horizontalSpacing: horizontalSpacing) { + GridRow { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + } + } + .layoutDirection(layoutDirection) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildMultipleChildrenSingleRowDifferentSizes(horizontalSpacing: CGFloat = 0, rowAlignment: VerticalAlignment = .center) { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let layout = Grid(horizontalSpacing: horizontalSpacing) { + GridRow(alignment: rowAlignment) { + view1.frame(width: 50, height: 50) + view2.frame(width: 100, height: 100) + view3.frame(width: 50, height: 50) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildMultipleChildrenSingleRowUnbounded(horizontalSpacing: CGFloat = 0) { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let layout = Grid(horizontalSpacing: horizontalSpacing) { + GridRow { + view1 + view2 + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + func buildMultipleChildrenSingleRowOneUnboundedView() { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.red, text: "3") + + let layout = Grid { + GridRow { + view1 + .frame(width: 50, height: 50) + view2 + view3 + .frame(width: 50, height: 50) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + // Multiple children multiple rows cases + + func buildMultipleChildrenMultipleRows(verticalSpacing: CGFloat = 0, horizontalSpacing: CGFloat = 0) { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.red, text: "3") + + let view4 = ColorView(ColorPallete.blue, text: "1") + let view5 = ColorView(ColorPallete.orange, text: "2") + let view6 = ColorView(ColorPallete.blue, text: "3") + + let layout = Grid(horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing) { + GridRow { + view1.frame(width: 50, height: 50) + view2.frame(width: 50, height: 50) + view3.frame(width: 50, height: 50) + } + GridRow { + view4.frame(width: 50, height: 50) + view5.frame(width: 50, height: 50) + view6.frame(width: 50, height: 50) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildMultipleChildrenMultipleRowsUnbounded(verticalSpacing: CGFloat = 0, horizontalSpacing: CGFloat = 0) { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.red, text: "3") + + let view4 = ColorView(ColorPallete.red, text: "4") + let view5 = ColorView(ColorPallete.yellow, text: "4") + let view6 = ColorView(ColorPallete.red, text: "6") + + let layout = Grid(horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing) { + GridRow { + view1 + view2 + view3 + } + GridRow { + view4 + view5 + view6 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildMultipleChildrenSingleRowPartialWrapping(horizontalSpacing: CGFloat = 0, verticalSpacing: CGFloat = 0) { + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 50, height: 50) + + let longLabel = UILabel() + longLabel.text = "This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space." + longLabel.font = .systemFont(ofSize: 10) + longLabel.numberOfLines = 0 + + let view3 = ColorView(ColorPallete.yellow, text: "2") + .frame(width: 50, height: 50) + + let layout = Grid(horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing) { + GridRow { + view1 + longLabel + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 500, height: 500)) + ) + + } + + func buildMultipleChildrenSingleRowPartialWrappingWithUIView(horizontalSpacing: CGFloat = 0, verticalSpacing: CGFloat = 0) { + + func buildLongLabel() -> UILabel { + let longLabel = UILabel() + longLabel.text = "This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space. This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space. This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space." + longLabel.font = .systemFont(ofSize: 10) + longLabel.numberOfLines = 0 + return longLabel + } + + let longLabel = buildLongLabel() + let longLabel2 = buildLongLabel() + + let view = ColorView(ColorPallete.yellow, text: "2") + + let layout = Grid(horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing) { + GridRow { + longLabel + view + longLabel2 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + + } + + func buildMultipleChildrenSingleRowTwoPartialWrappingLabels(horizontalSpacing: CGFloat = 0, verticalSpacing: CGFloat = 0) { + + func buildLongLabel(color: UIColor) -> UILabel { + let longLabel = UILabel() + longLabel.text = "This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space. This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space. This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space." + longLabel.backgroundColor = color + longLabel.font = .systemFont(ofSize: 10) + longLabel.numberOfLines = 0 + return longLabel + } + + let longLabel = buildLongLabel(color: .blue) + let longLabel2 = buildLongLabel(color: .yellow) + + let layout = Grid(horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing) { + GridRow { + longLabel + + longLabel2 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + + } + + func buildUnboundedViewsWithLayoutPriority(view1LayoutPriority: CGFloat = 0, view2LayoutPriority: CGFloat = 0, view3LayoutPriority: CGFloat = 0) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.yellow, text: "2") + let view3 = ColorView(ColorPallete.red, text: "3") + + let layout = Grid { + GridRow { + view1 + .layoutPriority(view1LayoutPriority) + view2 + .layoutPriority(view2LayoutPriority) + view3 + .layoutPriority(view3LayoutPriority) + } + + } + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildMultipleChildrenMultipleRowswithGridAlignment(alignment: Alignment) { + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 50, height: 50) + + let view2 = ColorView(ColorPallete.blue, text: "1") + .frame(width: 100, height: 100) + + let view3 = ColorView(ColorPallete.red, text: "1") + .frame(width: 50, height: 50) + + let view4 = ColorView(ColorPallete.blue, text: "1") + .frame(width: 100, height: 100) + + let view5 = ColorView(ColorPallete.red, text: "1") + .frame(width: 50, height: 50) + + let view6 = ColorView(ColorPallete.blue, text: "1") + .frame(width: 100, height: 100) + + let layout = Grid(alignment: alignment) { + + GridRow { + view1 + view2 + view3 + } + + GridRow { + view4 + view5 + view6 + } + + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildGridCellAnchorGrid(alignment: Alignment) { + + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 100, height: 100) + let view2 = ColorView(ColorPallete.red, text: "2") + .frame(width: 100, height: 100) + + let view3 = ColorView(ColorPallete.red, text: "3") + .frame(width: 100, height: 100) + + let view4 = ColorView(ColorPallete.blue, text: "4") + .frame(width: 50, height: 50) + + let layout = Grid { + GridRow { + view1 + view2 + + } + GridRow { + view3 + view4 + .gridCellAnchor(alignment) + + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + + } + + func buildGridCellAnchorGridUnitPoints(_ unitPoint: UnitPoint, layoutDirection: LayoutDirection = .leftToRight) { + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 100, height: 100) + let view2 = ColorView(ColorPallete.red, text: "2") + .frame(width: 100, height: 100) + + let view3 = ColorView(ColorPallete.red, text: "3") + .frame(width: 100, height: 100) + + let view4 = ColorView(ColorPallete.blue, text: "4") + .frame(width: 50, height: 50) + + let layout = Grid { + GridRow { + view1 + view2 + + } + GridRow { + view3 + view4 + .gridCellAnchor(unitPoint) + + } + } + .layoutDirection(layoutDirection) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + + } + + func buildRowAlignmentOverridesGridAlignment() { + + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 100, height: 100) + let view2 = ColorView(ColorPallete.blue, text: "2") + .frame(width: 50, height: 50) + + let view3 = ColorView(ColorPallete.red, text: "3") + .frame(width: 75, height: 75) + + let view4 = ColorView(ColorPallete.yellow, text: "4") + .frame(width: 75, height: 75) + + let view5 = ColorView(ColorPallete.blue, text: "5") + .frame(width: 50, height: 50) + + let view6 = ColorView(ColorPallete.yellow, text: "6") + .frame(width: 100, height: 100) + + let layout = Grid(alignment: .top, horizontalSpacing: 10) { + GridRow { + view1 + view2 + view3 + + } + GridRow(alignment: .bottom) { + view4 + view5 + view6 + + } + + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + + } + + func buildGridCellAnchorGridOverrideRowAlignment(alignment: Alignment) { + + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 100, height: 100) + let view2 = ColorView(ColorPallete.red, text: "2") + .frame(width: 100, height: 100) + + let view3 = ColorView(ColorPallete.red, text: "3") + .frame(width: 100, height: 100) + + let view4 = ColorView(ColorPallete.yellow, text: "4") + .frame(width: 100, height: 100) + + let view5 = ColorView(ColorPallete.blue, text: "5") + .frame(width: 50, height: 50) + + let view6 = ColorView(ColorPallete.blue, text: "6") + .frame(width: 50, height: 50) + + let layout = Grid { + GridRow { + view1 + view2 + view3 + + } + GridRow(alignment: .top) { + view4 + view5 + view6 + .gridCellAnchor(.bottom) + + } + + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + + } + + // MARK: - Column Alignment Cases + + func buildGridColumnAlignment(alignment: HorizontalAlignment) { + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 100, height: 100) + + let view2 = ColorView(ColorPallete.yellow, text: "2") + .frame(width: 75, height: 75) + + let view3 = ColorView(ColorPallete.blue, text: "3") + .frame(width: 50, height: 50) + + let layout = Grid { + GridRow { + view1 + .gridColumnAlignment(alignment) + } + GridRow { + view2 + } + GridRow { + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildGridColumnAlignmentSecondRow(alignment: HorizontalAlignment) { + let view1 = ColorView(ColorPallete.red, text: "1") + .frame(width: 100, height: 100) + + let view2 = ColorView(ColorPallete.yellow, text: "2") + .frame(width: 75, height: 75) + + let view3 = ColorView(ColorPallete.blue, text: "3") + .frame(width: 50, height: 50) + + let layout = Grid { + GridRow { + view1 + } + GridRow { + view2 + .gridColumnAlignment(alignment) + + } + GridRow { + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + // Mark: - Test Cases + + // Single child Single Row + + func testSingleChildSingleRow() { + buildSingleChildSingleRow() + } + + func testSingleChildSingleRowUnbounded() { + buildSingleChildSingleRowUnbounded() + } + + // Single Child Multiple Row + + func testSingleChildMultipleRow() { + buildSingleChildMultipleRow() + } + + func testSingleChildMultipleRowWithVerticalSpacing() { + buildSingleChildMultipleRow(verticalSpacing: 10) + } + + // Multiple Children Single Row + + func testMultipleChildrenSingleRow() { + buildMultipleChildrenSingleRow() + } + + func testMultipleChildrenSingleRowRTL() { + buildMultipleChildrenSingleRow(layoutDirection: .leftToRight) + } + + func testMultipleChildrenSingleRowWithHorizontalSpacing() { + buildMultipleChildrenSingleRow(horizontalSpacing: 10) + } + + func testMultipleChildrenSingleRowWithHorizontalSpacingRTL() { + buildMultipleChildrenSingleRow(horizontalSpacing: 10, layoutDirection: .rightToLeft) + } + + func testMultipleChildrenSingleRowDifferentSizes() { + buildMultipleChildrenSingleRowDifferentSizes() + } + + func testMultipleChildrenSingleRowDifferentSizesWithHorizontalSpacing() { + buildMultipleChildrenSingleRowDifferentSizes(horizontalSpacing: 10) + } + + func testMultipleChildrenSingleRowUnbounded() { + buildMultipleChildrenSingleRowUnbounded() + } + + func testMultipleChildrenSingleRowUnboundedWithHorizontalSpacing() { + buildMultipleChildrenSingleRowUnbounded(horizontalSpacing: 10) + } + + func testMultipleChildrenSingleRowOneUnboundedView() { + buildMultipleChildrenSingleRowOneUnboundedView() + } + + // Multiple Children Multiple Rows + + func testMultipleChildrenMultipleRows() { + buildMultipleChildrenMultipleRows() + } + + func testMultipleChildrenMultipleRowsWithVerticalSpacing() { + buildMultipleChildrenMultipleRows(verticalSpacing: 10) + } + + func testMultipleChildrenMultipleRowsWithHorizontalSpacing() { + buildMultipleChildrenMultipleRows(horizontalSpacing: 10) + } + + func testMultipleChildrenMultipleRowsWithVerticalAndHorizontalSpacing() { + buildMultipleChildrenMultipleRows(verticalSpacing: 10, horizontalSpacing: 10) + } + + func testMultipleChildrenMultipleRowsUnbounded() { + buildMultipleChildrenMultipleRowsUnbounded() + } + + func testMultipleChildrenMultipleRowsUnboundedWithVerticalSpacing() { + buildMultipleChildrenMultipleRowsUnbounded(verticalSpacing: 10) + } + + func testMultipleChildrenMultipleRowsUnboundedWithHorizontalSpacing() { + buildMultipleChildrenMultipleRowsUnbounded(horizontalSpacing: 10) + } + + func testMultipleChildrenMultipleRowsUnboundedWithVerticalAndHorizontalSpacing() { + buildMultipleChildrenMultipleRowsUnbounded(verticalSpacing: 10, horizontalSpacing: 10) + } + + // Partial flexible wrapping cases + + func testMultipleChildrenSingleRowPartialWrapping() { + buildMultipleChildrenSingleRowPartialWrapping() + } + + func testMultipleChildrenSingleRowPartialWrappingWithHorizontalSpacing() { + buildMultipleChildrenSingleRowPartialWrapping(horizontalSpacing: 10) + } + + func testMultipleChildrenSingleRowPartialWrappingWithVerticalSpacing() { + buildMultipleChildrenSingleRowPartialWrapping(verticalSpacing: 10) + } + + func testMultipleChildrenSingleRowPartialWrappingWithVerticalAndHorizontalSpacing() { + buildMultipleChildrenSingleRowPartialWrapping(horizontalSpacing: 10, verticalSpacing: 10) + } + + func testMultipleChildrenSingleRowPartialWrappingUIView() { + buildMultipleChildrenSingleRowPartialWrappingWithUIView() + } + + func testMultipleChildrenSingleRowPartialWrappingUIViewWithHorizontalSpacing() { + buildMultipleChildrenSingleRowPartialWrappingWithUIView(horizontalSpacing: 10) + } + + func testMultipleChildrenSingleRowPartialWrappingUIViewWithVerticalSpacing() { + buildMultipleChildrenSingleRowPartialWrappingWithUIView(verticalSpacing: 10) + } + + func testMultipleChildrenSingleRowPartialWrappingUIViewWithVerticalAndHorizontalSpacing() { + buildMultipleChildrenSingleRowPartialWrappingWithUIView(horizontalSpacing: 10, verticalSpacing: 10) + } + + func testMultipleChildrenSingleRowTwoPartialWrappingLabels() { + buildMultipleChildrenSingleRowTwoPartialWrappingLabels() + } + + func testMultipleChildrenSingleRowTwoPartialWrappingLabelsWithHorizontalSpacing() { + buildMultipleChildrenSingleRowTwoPartialWrappingLabels(horizontalSpacing: 10) + } + + func testMultipleChildrenSingleRowTwoPartialWrappingLabelsWithVerticalSpacing() { + buildMultipleChildrenSingleRowTwoPartialWrappingLabels(verticalSpacing: 10) + } + + func testMultipleChildrenSingleRowTwoPartialWrappingLabelsWithVerticalAndHorizontalSpacing() { + buildMultipleChildrenSingleRowTwoPartialWrappingLabels(horizontalSpacing: 10, verticalSpacing: 10) + } + + // Layout Priority cases + + func testUnboundedViewsLayoutPriorityView1Highest() { + buildUnboundedViewsWithLayoutPriority(view1LayoutPriority: 1) + } + + func testUnboundedViewsLayoutPriorityView1Lowest() { + buildUnboundedViewsWithLayoutPriority(view1LayoutPriority: -1) + } + + func testUnboundedViewsLayoutPriorityView1andView2HigherPriority() { + buildUnboundedViewsWithLayoutPriority(view1LayoutPriority: 1, view2LayoutPriority: 1) + } + + func testUnboundedViewsLayoutPriorityEqualLayoutPriority() { + buildUnboundedViewsWithLayoutPriority(view1LayoutPriority: 5, view2LayoutPriority: 5, view3LayoutPriority: 5) + } + + // Grid alignment cases + + func testMultipleChildrenMultipleRowsWithGridAlignmentTop() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .top) + } + func testMultipleChildrenMultipleRowsWithGridAlignmentTopLeading() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .topLeading) + } + func testMultipleChildrenMultipleRowsWithGridAlignmentTopTrailing() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .topTrailing) + } + func testMultipleChildrenMultipleRowsWithGridAlignmentBottom() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .bottom) + } + func testMultipleChildrenMultipleRowsWithGridAlignmentBottomLeading() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .bottomLeading) + } + func testMultipleChildrenMultipleRowsWithGridAlignmentBottomTrailing() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .bottomTrailing) + } + func testMultipleChildrenMultipleRowsWithGridAlignmentLeading() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .leading) + } + func testMultipleChildrenMultipleRowsWithGridAlignmentTrailing() { + buildMultipleChildrenMultipleRowswithGridAlignment(alignment: .trailing) + } + + // Row alignment cases + + func testMultipleChildrenSingleRowDifferentSizesWithTopAlignment() { + buildMultipleChildrenSingleRowDifferentSizes(rowAlignment: .top) + } + + func testMultipleChildrenSingleRowDifferentSizesWithBottomAlignment() { + buildMultipleChildrenSingleRowDifferentSizes(rowAlignment: .bottom) + } + + func testRowAlignmentOverridesGridAlignment() { + buildRowAlignmentOverridesGridAlignment() + } + + //Column alignment cases + + func testGridColumnAlignmentLeading() { + buildGridColumnAlignment(alignment: .leading) + } + + func testGridColumnAlignmentTrailing() { + buildGridColumnAlignment(alignment: .trailing) + } + + func testGridColumnAlignmentNotFirstRowLeading() { + buildGridColumnAlignmentSecondRow(alignment: .leading) + } + + func testGridColumnAlignmentNotFirstRowTrailing() { + buildGridColumnAlignmentSecondRow(alignment: .trailing) + } + + //Grid cell anchor cases + + func testGridCellAnchorTopLeading() { + buildGridCellAnchorGrid(alignment: .topLeading) + } + + func testGridCellAnchorTopTrailing() { + buildGridCellAnchorGrid(alignment: .topTrailing) + } + + func testGridCellAnchorBottomLeading() { + buildGridCellAnchorGrid(alignment: .bottomLeading) + } + + func testGridCellAnchorBottomTrailing() { + buildGridCellAnchorGrid(alignment: .bottomTrailing) + } + + func testGridCellAnchorCenter() { + buildGridCellAnchorGrid(alignment: .center) + } + + func testGridCellAnchorTop() { + buildGridCellAnchorGrid(alignment: .top) + } + + func testGridCellAnchorBottom() { + buildGridCellAnchorGrid(alignment: .bottom) + } + + func testGridCellAnchorLeading() { + buildGridCellAnchorGrid(alignment: .leading) + } + + func testGridCellAnchorTrailing() { + buildGridCellAnchorGrid(alignment: .trailing) + } + + func testGridCellAnchorUnitPointTopLeading() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 0, y: 0)) + } + + func testGridCellAnchorUnitPointTopTrailing() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 0, y: 1)) + } + + func testGridCellAnchorUnitPointBottomTrailingRTL() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 1, y: 1), layoutDirection: .rightToLeft) + } + + func testGridCellAnchorUnitPointBottomLeading() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 1, y: 0)) + } + + func testGridCellAnchorUnitPointBottomTrailing() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 1, y: 1)) + } + + func testGridCellAnchorUnitPointCenter() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 0.5, y: 0.5)) + } + + func testGridCellAnchorUnitPointTop() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 0.5, y: 0)) + } + + func testGridCellAnchorUnitPointBottom() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 0.5, y: 1)) + } + + func testGridCellAnchorUnitPointLeading() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 0, y: 0.5)) + } + + func testGridCellAnchorUnitPointTrailing() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 1, y: 0.5)) + } + + func testGridCellAnchorUnitPointCustom() { + buildGridCellAnchorGridUnitPoints(UnitPoint(x: 0.25, y: 0.75)) + } + + func testGridCellAnchorOverrideRowAlignment() { + buildGridCellAnchorGridOverrideRowAlignment(alignment: .bottom) + } + + func testGridWithFixedSizeElementsAndSpacerInBetween() { + let view1 = ColorView(ColorPallete.blue, text: "1") + let view2 = ColorView(ColorPallete.blue, text: "2") + + let layout = Grid(horizontalSpacing: 10, verticalSpacing: 10) { + GridRow { + view1 + .frame(width: 50, height: 50) + Spacer() + view2 + .frame(width: 50, height: 50) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testGridWithFixedSizeElementsAndSpacerOnTheLeft() { + let view1 = ColorView(ColorPallete.blue, text: "1") + let view2 = ColorView(ColorPallete.blue, text: "2") + + let layout = Grid(horizontalSpacing: 10, verticalSpacing: 10) { + GridRow { + Spacer() + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testGridWithFixedSizeElementsAndSpacerOnTheRight() { + let view1 = ColorView(ColorPallete.blue, text: "1") + let view2 = ColorView(ColorPallete.blue, text: "2") + + let layout = Grid(horizontalSpacing: 10, verticalSpacing: 10) { + GridRow { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + Spacer() + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testGridTwoLinesWithFixedSizeElementsAndSpacerOnTheRight() { + let view1 = ColorView(ColorPallete.blue, text: "1") + let view2 = ColorView(ColorPallete.blue, text: "2") + + let layout = Grid(horizontalSpacing: 10, verticalSpacing: 10) { + GridRow { + Spacer() + view1 + .frame(width: 50, height: 50) + } + GridRow { + Spacer() + view2 + .frame(width: 100, height: 100) + } + + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testGridTwoLinesWithFixedSizeElementsAndSpacerOnTheRight2() { + let view1 = ColorView(ColorPallete.blue, text: "1") + let view2 = ColorView(ColorPallete.blue, text: "2") + + let view3 = ColorView(ColorPallete.blue, text: "3") + let view4 = ColorView(ColorPallete.blue, text: "4") + + let layout = Grid(horizontalSpacing: 10, verticalSpacing: 10) { + GridRow { + view1 + .frame(width: 20, height: 20) + Spacer() + view2 + .frame(width: 50, height: 50) + } + GridRow { + view3 + .frame(width: 20, height: 20) + .gridCellAnchor(.topLeading) + Spacer() + view4 + .frame(width: 100, height: 100) + } + + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testLongLabelWithSpacerInBetween() { + + func buildLongLabel(color: UIColor) -> UILabel { + let longLabel = UILabel() + longLabel.text = "This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space. This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space. This is a long label that will wrap to multiple lines and will be truncated at the end of the line if it is too long to fit in the available space." + longLabel.backgroundColor = color + longLabel.font = .systemFont(ofSize: 10) + longLabel.numberOfLines = 0 + return longLabel + } + + let longLabel = buildLongLabel(color: .blue) + let longLabel2 = buildLongLabel(color: .yellow) + + let layout = Grid { + GridRow { + longLabel + Spacer() + longLabel2 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + + } + + func buildGridInvoiceLayout(shortDescriptions: Bool = false, addSpacerAfaterDescriptionHeader: Bool = false, addSpacerAfterDescriptionLabel: Bool = false, addEmptyLayoutAfterDescriptionLabel: Bool = false, size: CGSize = CGSize(width: 300, height: 600)) { + let titleLabel1 = UILabel() + titleLabel1.text = "Item" + titleLabel1.font = .boldSystemFont(ofSize: 18) + titleLabel1.numberOfLines = 0 + + let titleLabel2 = UILabel() + titleLabel2.text = "Description" + titleLabel2.font = .boldSystemFont(ofSize: 18) + titleLabel2.numberOfLines = 0 + + let titleLabel3 = UILabel() + titleLabel3.text = "Quantity" + titleLabel3.font = .boldSystemFont(ofSize: 18) + titleLabel3.numberOfLines = 0 + + let titleLabel4 = UILabel() + titleLabel4.text = "Price" + titleLabel4.font = .boldSystemFont(ofSize: 18) + titleLabel4.numberOfLines = 0 + + let itemLabel1 = UILabel() + itemLabel1.text = "Bread" + itemLabel1.font = .systemFont(ofSize: 16) + itemLabel1.numberOfLines = 0 + + let itemLabel2 = UILabel() + itemLabel2.text = "Milk" + itemLabel2.font = .systemFont(ofSize: 16) + itemLabel2.numberOfLines = 0 + + let itemLabel3 = UILabel() + itemLabel3.text = "Water" + itemLabel3.font = .systemFont(ofSize: 16) + itemLabel3.numberOfLines = 0 + + let descriptionLabel1 = UILabel() + descriptionLabel1.text = shortDescriptions ? "Fresh" : "Bread is a staple food made from crop" + descriptionLabel1.font = .systemFont(ofSize: 16) + descriptionLabel1.numberOfLines = 0 + + let descriptionLabel2 = UILabel() + descriptionLabel2.text = shortDescriptions ? "White" : "Milk is a white, nutritious drink made by cows" + descriptionLabel2.font = .systemFont(ofSize: 16) + descriptionLabel2.numberOfLines = 0 + + let descriptionLabel3 = UILabel() + descriptionLabel3.text = shortDescriptions ? "Liquid" : "Water is a transparent, tasteless, odorless liquid" + descriptionLabel3.font = .systemFont(ofSize: 16) + descriptionLabel3.numberOfLines = 0 + + let quantityLabel1 = UILabel() + quantityLabel1.text = "Qty 11" + quantityLabel1.font = .systemFont(ofSize: 16) + quantityLabel1.numberOfLines = 0 + + let quantityLabel2 = UILabel() + quantityLabel2.text = "Qty 32" + quantityLabel2.font = .systemFont(ofSize: 16) + quantityLabel2.numberOfLines = 0 + + let quantityLabel3 = UILabel() + quantityLabel3.text = "Qty 3" + quantityLabel3.font = .systemFont(ofSize: 16) + quantityLabel3.numberOfLines = 0 + + let priceLabel1 = UILabel() + priceLabel1.text = "$10.00" + priceLabel1.font = .systemFont(ofSize: 16) + priceLabel1.numberOfLines = 0 + + let priceLabel2 = UILabel() + priceLabel2.text = "$20.00" + priceLabel2.font = .systemFont(ofSize: 16) + priceLabel2.numberOfLines = 0 + + let priceLabel3 = UILabel() + priceLabel3.text = "$30.00" + priceLabel3.font = .systemFont(ofSize: 16) + priceLabel3.numberOfLines = 0 + + let layout = Grid(alignment: .top, horizontalSpacing: 4, verticalSpacing: 4) { + GridRow { + titleLabel1 + .gridColumnAlignment(.leading) + titleLabel2 + .gridColumnAlignment(.leading) + + if addSpacerAfaterDescriptionHeader { + Spacer() + } + titleLabel3 + .gridColumnAlignment(.trailing) + titleLabel4 + .gridColumnAlignment(.trailing) + } + + GridRow { + itemLabel1 + descriptionLabel1 + if addSpacerAfterDescriptionLabel { + Spacer() + } else if addEmptyLayoutAfterDescriptionLabel { + EmptyLayout() + } + quantityLabel1 + priceLabel1 + } + + GridRow { + itemLabel2 + descriptionLabel2 + if addSpacerAfterDescriptionLabel { + Spacer() + } else if addEmptyLayoutAfterDescriptionLabel { + EmptyLayout() + } + quantityLabel2 + priceLabel2 + } + + GridRow { + itemLabel3 + descriptionLabel3 + if addSpacerAfterDescriptionLabel { + Spacer() + } else if addEmptyLayoutAfterDescriptionLabel { + EmptyLayout() + } + quantityLabel3 + priceLabel3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(size) + ) + } + func testGridInvoiceLayout() { + buildGridInvoiceLayout() + } + + func testGridInvoiceLayoutWithShortDescription() { + buildGridInvoiceLayout(shortDescriptions: true) + } + + func testGridInvoiceLayoutWithShortDescriptionWithSpacers() { + buildGridInvoiceLayout(shortDescriptions: true, addSpacerAfaterDescriptionHeader: true, addSpacerAfterDescriptionLabel: true, size: CGSize(width: 600, height: 600)) + } + + func testNotEqualAmountOfColumns() { + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.red, text: "2") + let view3 = ColorView(ColorPallete.red, text: "3") + + let layout = Grid { + GridRow { + view1 + .frame(width: 100, height: 100) + view2 + .frame(width: 100, height: 100) + } + + GridRow { + view3 + .frame(width: 100, height: 100) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testThatTheTextWillBeRemeasuredAfterLargerFixdSizeElement() { + let view1 = ColorView(ColorPallete.orange, text: "1") + let view2 = ColorView(ColorPallete.blue, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + + let titleLabel1 = UILabel() + titleLabel1.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + titleLabel1.font = .boldSystemFont(ofSize: 18) + titleLabel1.numberOfLines = 0 + + let layout = Grid { + GridRow { + view1 + .frame(width: 100, height: 10) + view2 + .frame(width: 100, height: 10) + view3 + .frame(width: 100, height: 10) + } + + GridRow { + titleLabel1 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testThatTheTextWillBeRemeasuredAfterLargerFixdSizeElementVertically() { + let view1 = ColorView(ColorPallete.orange, text: "1") + let view2 = ColorView(ColorPallete.blue, text: "2") + let view3 = ColorView(ColorPallete.yellow, text: "3") + + let titleLabel1 = UILabel() + titleLabel1.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + titleLabel1.font = .boldSystemFont(ofSize: 18) + titleLabel1.numberOfLines = 0 + + let layout = Grid { + GridRow { + view1 + .frame(width: 10, height: 100) + titleLabel1 + } + + GridRow { + view2 + .frame(width: 10, height: 100) + } + + GridRow { + view3 + .frame(width: 10, height: 100) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testEmptyGrid() { + let v = UIView() + let grid1 = Grid { + + } + + let grid2 = Grid { + GridRow { + + } + } + + let layout = VStack { + grid1 + grid2 + v.frame(width: 1, height: 1) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testJustLabel() { + let label = UILabel() + label.text = "Lorem ipsum dolor" + label.font = .boldSystemFont(ofSize: 18) + label.numberOfLines = 0 + + let layout = Grid { + GridRow { + label + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testJustTwoLabelInSingleRow() { + let label = UILabel() + label.text = "Lorem ipsum dolor" + label.font = .boldSystemFont(ofSize: 18) + label.numberOfLines = 0 + + let label2 = UILabel() + label2.text = "Lorem ipsum dolor sit amet elit" + label2.font = .boldSystemFont(ofSize: 18) + label2.numberOfLines = 0 + + let layout = Grid { + GridRow { + label + label2 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testJustTwoLabelInSingleColumn() { + let label = UILabel() + label.text = "Lorem ipsum dolor" + label.font = .boldSystemFont(ofSize: 18) + label.numberOfLines = 0 + + let label2 = UILabel() + label2.text = "Lorem ipsum dolor sit amet elit" + label2.font = .boldSystemFont(ofSize: 18) + label2.numberOfLines = 0 + + let layout = Grid { + GridRow { + label + } + GridRow { + label2 + } + GridRow { + + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testWithSliders() { + let label = UILabel() + label.text = "Lorem ipsum" + label.font = .boldSystemFont(ofSize: 18) + label.numberOfLines = 0 + + let label2 = UILabel() + label2.text = "Lorem ipsum dolor sit" + label2.font = .boldSystemFont(ofSize: 18) + label2.numberOfLines = 0 + + let slider1 = UISlider() + slider1.minimumValue = 0 + slider1.maximumValue = 100 + slider1.value = 60 + + let slider2 = UISlider() + slider2.minimumValue = 0 + slider2.maximumValue = 100 + slider2.value = 40 + + let layout = Grid(alignment: .leading, horizontalSpacing: 10) { + GridRow { + label + slider1 + } + GridRow { + label2 + slider2 + } + } + .padding(.horizontal, 16) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 400, height: 300)) + ) + } + +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HFlowServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HFlowServerSnapshotTests.swift new file mode 100644 index 0000000..65a89d1 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HFlowServerSnapshotTests.swift @@ -0,0 +1,709 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +final class HFlowServerSnapShotTests: FBServerSnapshotTestCase { + + func buildHFlowSingleChild( + itemAlignment: VerticalAlignment, + lineAlignment: HorizontalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat + ) { + + let view = ColorView(ColorPallete.red, text: "1") + + let HFlow = HFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view + } + + takeSnapshot( + with: HFlow, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildHFlowSingleFixedChild( + itemAlignment: VerticalAlignment, + lineAlignment: HorizontalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat + ) { + + let view = ColorView(ColorPallete.red, text: "1") + + let HFlow = HFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view.frame(width: 100, height: 100) + } + + takeSnapshot( + with: HFlow, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: VerticalAlignment, lineAlignment: HorizontalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat, + layoutDirection: LayoutDirection = .leftToRight + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(.green, text: "5") + + let HFlow = HFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + view4 + .frame(width: 50, height: 50) + view5 + .frame(width: 50, height: 50) + } + .layoutDirection(layoutDirection) + + takeSnapshot( + with: HFlow, + in: .proposed(CGSize(width: 150, height: 150)) + ) + + } + + func buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: VerticalAlignment, + lineAlignment: HorizontalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat, + proposedSize: CGSize = CGSize(width: 200, height: 200) + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let HFlow = HFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + + } + + takeSnapshot( + with: HFlow, + in: .proposed(proposedSize) + ) + + } + + func buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: VerticalAlignment, + lineAlignment: HorizontalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let HFlow = HFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 80, height: 50) + view2 + .frame(width: 30, height: 70) + view3 + .frame(width: 50, height: 40) + } + + takeSnapshot( + with: HFlow, + in: .proposed(CGSize(width: 200, height: 200)) + ) + + } + + func buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: VerticalAlignment, + lineAlignment: HorizontalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(.green, text: "5") + + let HFlow = HFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 80, height: 50) + view2 + .frame(width: 30, height: 70) + view3 + .frame(width: 50, height: 40) + view4 + .frame(width: 50, height: 50) + view5 + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: HFlow, + in: .proposed(CGSize(width: 200, height: 200)) + ) + + } + + // MARK: - Single Child + func testSingleChild() { + buildHFlowSingleChild( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + // MARK: - Multiple Children Same Size Single Line + + func testMultipleChildrenSameSizeSingleLine() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeSingleLineItemAlignmentTop() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .top, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeSingleLineItemAlignmentBottom() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .bottom, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeSingleLineItemSpacing() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeSingleLineLineSpacing() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10) + } + + func testMultipleChildrenSameSizeSingleLineItemAndLineSpacing() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + // MARK: - Multiple Children with different proposed size + func testMultipleChildrenSameSizeSingleLine150x150() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0, + proposedSize: CGSize(width: 150, height: 150) + ) + } + + func testMultipleChildrenSameSizeSingleLine149x150() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0, + proposedSize: CGSize(width: 149, height: 150) + ) + } + + func testMultipleChildrenSameSizeSingleLine99x150() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0, + proposedSize: CGSize(width: 99, height: 150) + ) + } + + func testMultipleChildrenSameSizeSingleLine49x150() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0, + proposedSize: CGSize(width: 49, height: 150) + ) + } + + func testMultipleChildrenSameSizeSingleLine170x150WithItemSpacing() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0, + proposedSize: CGSize(width: 170, height: 150) + ) + } + + func testMultipleChildrenSameSizeSingleLine169x150WithItemSpacing() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0, + proposedSize: CGSize(width: 169, height: 150) + ) + } + + func testMultipleChildrenSameSizeSingleLine110x150WithItemSpacing() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0, + proposedSize: CGSize(width: 110, height: 150) + ) + } + + func testMultipleChildrenSameSizeSingleLine109x150WithItemSpacing() { + buildHflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0, + proposedSize: CGSize(width: 109, height: 150) + ) + } + + // MARK: - Multiple Children Same Size MultiLine + + func testMultipleChildren() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenItemAlignmentTop() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .top, + lineAlignment: .center, itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenItemAlignmentBottom() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .bottom, + lineAlignment: .center, itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenLineAlignmentLeading() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .leading, itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenLineAlignmentTrailing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenItemAlignmentTopLineAlignmentLeading() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .top, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenItemAlignmentTopLineAlingmentTrailing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .top, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenItemAlignmentBottomLineAlignmentLeading() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .bottom, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenItemAlignmentBottomLineAlignmentTrailing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .bottom, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenWithItemSpacing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0 + ) + } + + func testMultipleChildrenWithLineSpacing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10 + ) + } + + func testMultipleChildrenWithItemAndLineSpacing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + func testMultipleChildrenWithNegativeItemSpacing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: -10, + lineSpacing: 0 + ) + } + + func testMultipleChildrenWithNegativeLineSpacing() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: -10 + ) + } + + // MARK: - Multiple Children Different Size Single Line + + func testMultipleChildrenDifferentSizeSingleLine() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentTop() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .top, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentBottom() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .bottom, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineLineAlignmentLeading() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineLineAlignmentTrailing() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentTopLineAlignmentLeading() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .top, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentTopLineAlignmentTrailing() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .top, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentBottomLineAlignmentLeading() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .bottom, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentBottomLineAlignmentTrailing() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .bottom, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeWithItemSpacing() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeWithLineSpacing() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10 + ) + } + + func testMultipleChildrenDifferentSizeWithItemAndLineSpacing() { + buildHflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + // MARK: - Multiple Children Different Size MultiLine + + func testMultipleChildrenDifferentSizeMultiLine() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentTop() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .top, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentBottom() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .bottom, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineLineAlignmentLeading() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineLineAlignmentTrailing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentTopLineAlignmentLeading() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .top, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentTopLineAlignmentTrailing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .top, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentBottomLineAlignmentLeading() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .bottom, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentBottomLineAlignmentTrailing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .bottom, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineWithItemSpacing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineWithLineSpacing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineWithItemAndLineSpacing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineWithNegativeItemSpacing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: -10, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineWithNegativeLineSpacing() { + buildHflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: -10 + ) + } + + // MARK: - RTL + + func testMultipleChildrenRTL() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0, + layoutDirection: .rightToLeft + ) + } + + func testMultipleChildrenLineAlignmentLeadingRTL() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .leading, + itemSpacing: 0, + lineSpacing: 0, + layoutDirection: .rightToLeft + ) + } + + func testMultipleChildrenLineAlignmentTrailingRTL() { + buildHflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .trailing, + itemSpacing: 0, + lineSpacing: 0, + layoutDirection: .rightToLeft) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackRTLServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackRTLServerSnapshotTests.swift new file mode 100644 index 0000000..a3507f0 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackRTLServerSnapshotTests.swift @@ -0,0 +1,145 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator + +@testable import QuickLayoutBridge +@testable import QuickLayoutCore + +@MainActor +class HStackRTLServerSnapshotTests: FBServerSnapshotTestCase { + + func testWithThreeViews() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + + let layout = HStack { + view1 + .frame(width: 100, height: 100) + view2 + .frame(width: 80, height: 80) + view3 + .frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)), + layoutDirection: .rightToLeft + ) + } + + func testWithThreeViewsAndSpacing() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + + let layout = HStack(spacing: 10) { + view1 + .frame(width: 100, height: 100) + view2 + .frame(width: 80, height: 80) + view3 + .frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)), + layoutDirection: .rightToLeft + ) + } + + func testWithThreeViewsAndSpacers() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + + let layout = HStack { + view1 + .frame(width: 100, height: 100) + Spacer(40) + view2 + .frame(width: 80, height: 80) + Spacer(10) + view3 + .frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)), + layoutDirection: .rightToLeft + ) + } + + func testWithOneViewAndSpacerAfter() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1 + .frame(width: 100, height: 100) + Spacer(40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)), + layoutDirection: .rightToLeft + ) + } + + func testWithOneViewAndSpacerBefore() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + Spacer(40) + view1 + .frame(width: 100, height: 100) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)), + layoutDirection: .rightToLeft + ) + } + + func testWithSingleSpacer() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + Spacer(40) + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 100, height: 100)), + layoutDirection: .rightToLeft + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackServerSnapshotTests.swift new file mode 100644 index 0000000..79c2817 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackServerSnapshotTests.swift @@ -0,0 +1,51 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class HStackServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTest(alignment: VerticalAlignment) { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + + let layout = HStack(alignment: alignment) { + view1 + .frame(width: 100, height: 100) + view2 + .frame(width: 80, height: 80) + view3 + .frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testAlignmentCenter() { + runTest(alignment: .center) + } + + func testAlignmentTop() { + runTest(alignment: .top) + } + + func testAlignmentBottom() { + runTest(alignment: .bottom) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackWithTwoViewsAndInfinitSizeServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackWithTwoViewsAndInfinitSizeServerSnapshotTests.swift new file mode 100644 index 0000000..d415a8a --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackWithTwoViewsAndInfinitSizeServerSnapshotTests.swift @@ -0,0 +1,167 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class HStackWithTwoViewsAndInfinitSizeServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTest(on views: (UIView, UIView)) { + takeSnapshot( + with: HStack { + views.0 + views.1 + }, + in: .proposed(CGSize(width: CGFloat.infinity, height: .infinity)) + ) + } + + func testTwoLabelsInHStack() { + let view1 = UILabel() + view1.text = "Label 1" + + let view2 = UILabel() + view2.text = "Label 2" + + runTest(on: (view1, view2)) + } + + func testTwoUIViews() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUISliders() { + let view1 = UISlider() + view1.backgroundColor = ColorPallete.blue + + let view2 = UISlider() + view2.backgroundColor = ColorPallete.yellow + + // Overlapping shadows from the two sliders causes the snapshot to fail. So we need to add a Spacer between them. + takeSnapshot( + with: HStack { + view1 + Spacer(40) + view2 + }, + in: .proposed(CGSize(width: CGFloat.infinity, height: .infinity)) + ) + } + + func testTwoUIScrollView() { + let view1 = UIScrollView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIScrollView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUITextView() { + let view1 = UITextView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITextView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUICollectionView() { + let view1 = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + view1.backgroundColor = ColorPallete.blue + + let view2 = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUITextField() { + let view1 = UITextField() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITextField() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUISearchField() { + let view1 = UITextField() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITextField() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUIButton() { + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + + let view2 = UIButton(type: .system) + view2.setTitle("Button 2", for: .normal) + + runTest(on: (view1, view2)) + } + + func testTwoUISwitch() { + let view1 = UISwitch() + let view2 = UISwitch() + + runTest(on: (view1, view2)) + } + + func testTwoUIStepper() { + let view1 = UIStepper() + let view2 = UIStepper() + + runTest(on: (view1, view2)) + } + + func testTwoUIActivitiyIndicators() { + if #available(iOS 13.0, *) { + let view1 = UIActivityIndicatorView(style: .medium) + view1.backgroundColor = ColorPallete.blue + view1.startAnimating() + + let view2 = UIActivityIndicatorView(style: .medium) + view2.backgroundColor = ColorPallete.yellow + view2.startAnimating() + + runTest(on: (view1, view2)) + } + } + + func testTwoUIProgressView() { + let view1 = UIProgressView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIProgressView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUIImageView() { + let view1 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.blue, size: CGSize(width: 40, height: 40))) + let view2 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.yellow, size: CGSize(width: 40, height: 40))) + + runTest(on: (view1, view2)) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackWithTwoViewsServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackWithTwoViewsServerSnapshotTests.swift new file mode 100644 index 0000000..3c6fd2c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/HStackWithTwoViewsServerSnapshotTests.swift @@ -0,0 +1,169 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class HStackWithTwoViewsServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTest(on views: (UIView, UIView)) { + takeSnapshot( + with: HStack { + views.0 + views.1 + }, + in: .proposed(CGSize(width: 300, height: 100)) + ) + } + + func testTwoLabelsInHStack() { + let view1 = UILabel() + view1.text = "Label 1" + + let view2 = UILabel() + view2.text = "Label 2" + + runTest(on: (view1, view2)) + } + + func testTwoUIViews() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUISliders() { + let view1 = UISlider() + view1.backgroundColor = ColorPallete.blue + + let view2 = UISlider() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUIScrollView() { + let view1 = UIScrollView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIScrollView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUITextView() { + let view1 = UITextView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITextView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUICollectionView() { + let view1 = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + view1.backgroundColor = ColorPallete.blue + + let view2 = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUITablieViews() { + let view1 = UITableView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITableView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUITextField() { + let view1 = UITextField() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITextField() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUISearchField() { + let view1 = UITextField() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITextField() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUIButton() { + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + + let view2 = UIButton(type: .system) + view2.setTitle("Button 2", for: .normal) + + runTest(on: (view1, view2)) + } + + func testTwoUISwitch() { + let view1 = UISwitch() + let view2 = UISwitch() + + runTest(on: (view1, view2)) + } + + func testTwoUIStepper() { + let view1 = UIStepper() + let view2 = UIStepper() + + runTest(on: (view1, view2)) + } + + func testTwoUIActivitiyIndicators() { + if #available(iOS 13.0, *) { + let view1 = UIActivityIndicatorView(style: .medium) + view1.backgroundColor = ColorPallete.blue + view1.startAnimating() + + let view2 = UIActivityIndicatorView(style: .medium) + view2.backgroundColor = ColorPallete.yellow + view2.startAnimating() + + runTest(on: (view1, view2)) + } + } + + func testTwoUIProgressView() { + let view1 = UIProgressView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIProgressView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUIImageView() { + let view1 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.blue, size: CGSize(width: 40, height: 40))) + let view2 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.yellow, size: CGSize(width: 40, height: 40))) + + runTest(on: (view1, view2)) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/IdealLayoutServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/IdealLayoutServerSnapshotTests.swift new file mode 100644 index 0000000..1742328 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/IdealLayoutServerSnapshotTests.swift @@ -0,0 +1,216 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import QuickLayoutBridge +import XCTest + +@MainActor +class IdealLayoutServerSnapshotTests: FBServerSnapshotTestCase { + + func testEqualWidthLabels() { + let view1 = UILabel() + view1.text = "Lorem ipsum dolor" + view1.textColor = .white + view1.backgroundColor = ColorPallete.red + + let view2 = UILabel() + view2.text = "ipsum" + view2.textColor = .white + view2.backgroundColor = ColorPallete.blue + + let layout = VStack { + view1 + .resizable(axis: .horizontal) + Spacer(8) + view2 + .resizable(axis: .horizontal) + } + .idealLayout() + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 50)) + ) + } + + func testEqualWidthSingleLabel() { + let view1 = UILabel() + view1.text = "Lorem" + view1.textColor = .white + view1.backgroundColor = ColorPallete.red + + let layout = VStack { + view1 + .resizable(axis: .horizontal) + } + .idealLayout() + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 50)) + ) + } + + func testEqualWidthTwoLabelGetsTruncated() { + let view1 = UILabel() + view1.text = "Lorem ipsum dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor" + view1.textColor = .white + view1.backgroundColor = ColorPallete.red + + let view2 = UILabel() + view2.text = "ipsum" + view2.textColor = .white + view2.backgroundColor = ColorPallete.blue + + let layout = VStack { + view1 + .resizable(axis: .horizontal) + Spacer(8) + view2 + .resizable(axis: .horizontal) + } + .idealLayout() + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 100, height: 50)) + ) + } + + func testEqualWidthSingleLabelGetsTruncated() { + let view1 = UILabel() + view1.text = "Lorem ipsum dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor dolor" + view1.textColor = .white + view1.backgroundColor = ColorPallete.red + + let layout = VStack { + view1 + .resizable(axis: .horizontal) + } + .idealLayout() + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 100, height: 50)) + ) + } + + func testEqualHeightLabels() { + let view1 = UILabel() + view1.text = "Lorem\nipsum\ndolor" + view1.textColor = .white + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.red + + let view2 = UILabel() + view2.text = "ipsum" + view2.textColor = .white + view2.backgroundColor = ColorPallete.blue + + let layout = HStack { + view1 + .resizable(axis: .vertical) + Spacer(8) + view2 + .resizable(axis: .vertical) + } + .idealLayout() + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 100)) + ) + } + + func testEqualHeightLayouts() { + let view1 = UILabel() + view1.text = "Lorem\nipsum\ndolor\ndolor\ndolor" + view1.textColor = .white + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.red + + let view2 = UILabel() + view2.text = "ipsum" + view2.textColor = .white + view2.backgroundColor = ColorPallete.blue + + let view3 = UILabel() + view3.text = "ipsum" + view3.textColor = .white + view3.backgroundColor = ColorPallete.orange + + let layout = HStack { + view1 + .resizable(axis: .vertical) + Spacer(8) + + VStack { + view2 + Spacer() + view3 + } + } + .idealLayout() + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 500, height: 500)) + ) + } + + func testEqualWidthClampedToProposedSize() { + let view1 = UILabel() + view1.text = "Lorem ipsum dolor dolor dolor dolor" + view1.textColor = .white + view1.backgroundColor = ColorPallete.red + + let view2 = UILabel() + view2.text = "ipsum" + view2.textColor = .white + view2.backgroundColor = ColorPallete.blue + + let layout = VStack { + view1 + .resizable(axis: .horizontal) + Spacer(8) + view2 + .resizable(axis: .horizontal) + } + .idealLayout() + .frame(width: 200) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 50)) + ) + } + + func testFixedSizeViews() { + let view1 = ViewWithSize(customSize: CGSize(width: 50, height: 50)) + view1.backgroundColor = ColorPallete.red + + let view2 = ViewWithSize(customSize: CGSize(width: 50, height: 50)) + view2.backgroundColor = ColorPallete.blue + + let layout = VStack { + view1 + Spacer(8) + view2 + } + .idealLayout() + + _ = layout.sizeThatFits(CGSize(width: 300, height: 300)) + XCTAssertEqual(view1.proposedSizes.count, 1) + XCTAssertEqual(view2.proposedSizes.count, 1) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayeringServerSnaposhTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayeringServerSnaposhTests.swift new file mode 100644 index 0000000..ac4a676 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayeringServerSnaposhTests.swift @@ -0,0 +1,134 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +private class LabelView: UIView { + let label = UILabel() + + init(_ text: String) { + super.init(frame: .zero) + label.text = text + label.textColor = .white + self.addSubview(label) + } + + private lazy var layout: Layout = { + HStack { + label + } + }() + + public required init?(coder: NSCoder) { + fatalError() + } + + override func layoutSubviews() { + layout.applyFrame(bounds) + } +} + +@MainActor +class LayeringServerSnaposhTests: FBServerSnapshotTestCase { + + func testLayeringWithAlignment() { + + let label1 = UILabel() + label1.text = "Hello World" + + let targetView = UIView() + targetView.backgroundColor = ColorPallete.blue + + let view1 = LabelView("1") + view1.backgroundColor = ColorPallete.red + let view2 = LabelView("2") + view2.backgroundColor = ColorPallete.yellow + let view3 = LabelView("3") + view3.backgroundColor = ColorPallete.orange + + let view4 = LabelView("4") + view4.backgroundColor = ColorPallete.orange + let view5 = LabelView("5") + view5.backgroundColor = ColorPallete.red + let view6 = LabelView("6") + view6.backgroundColor = ColorPallete.yellow + + let view7 = LabelView("7") + view7.backgroundColor = ColorPallete.yellow + let view8 = LabelView("8") + view8.backgroundColor = ColorPallete.orange + let view9 = LabelView("9") + view9.backgroundColor = ColorPallete.red + + let layout = HStack { + label1 + Spacer(8) + targetView + .frame(width: 100, height: 100) + .overlay(alignment: .topLeading) { + view1.frame(width: 30, height: 30) + } + .overlay(alignment: .top) { + view2.frame(width: 30, height: 30) + } + .overlay(alignment: .topTrailing) { + view3.frame(width: 30, height: 30) + } + .overlay(alignment: .leading) { + view4.frame(width: 30, height: 30) + } + .overlay { + view5.frame(width: 30, height: 30) + } + .overlay(alignment: .trailing) { + view6.frame(width: 30, height: 30) + } + .overlay(alignment: .bottomLeading) { + view7.frame(width: 30, height: 30) + } + .overlay(alignment: .bottom) { + view8.frame(width: 30, height: 30) + } + .overlay(alignment: .bottomTrailing) { + view9.frame(width: 30, height: 30) + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 200, height: 200)) + ) + } + + func testLayeringWithNil() { + + let label1 = UILabel() + label1.text = "Hello World" + + let targetView = UIView() + targetView.backgroundColor = ColorPallete.blue + + let view1: UIView? = nil + + let layout = HStack { + label1 + Spacer(8) + targetView + .frame(width: 100, height: 100) + .overlay(alignment: .topLeading) { + view1?.frame(width: 30, height: 30) + } + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 200, height: 200)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayoutDirectionSnapShotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayoutDirectionSnapShotTests.swift new file mode 100644 index 0000000..660090e --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayoutDirectionSnapShotTests.swift @@ -0,0 +1,85 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import QuickLayoutBridge +import XCTest + +class LayoutDirectionSnapShotTests: FBServerSnapshotTestCase { + + func testOverrideLeftToRight() { + let label1 = UILabel() + let label2 = UILabel() + + label1.text = "Hello" + label2.text = "Goodbye" + + let layout = HStack { + label1 + Spacer() + label2 + } + .layoutDirection(.leftToRight) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 44)) + ) + } + + func testOverrideRightToLeft() { + let label1 = UILabel() + let label2 = UILabel() + + label1.text = "Hello" + label2.text = "Goodbye" + + let layout = HStack { + label1 + Spacer() + label2 + } + .layoutDirection(.rightToLeft) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 44)) + ) + } + + func testFlexibleFrameAlignment() { + let label1 = UILabel() + + label1.text = "Lorem Ipsum" + + let layout = + label1 + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .layoutDirection(.rightToLeft) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 44)) + ) + } + + func testFixedFrameAlignment() { + let label1 = UILabel() + + label1.text = "Lorem Ipsum" + + let layout = + label1 + .frame(width: 200, alignment: .leading) + .layoutDirection(.rightToLeft) + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 200, height: 44)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayoutPriorityServerSnapshTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayoutPriorityServerSnapshTests.swift new file mode 100644 index 0000000..8564f95 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/LayoutPriorityServerSnapshTests.swift @@ -0,0 +1,188 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class LayoutPriorityServerSnaposhTests: FBServerSnapshotTestCase { + + func testWithoutLayoutPriorities() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UIView() + view2.backgroundColor = ColorPallete.red + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let view4 = UIView() + view4.backgroundColor = ColorPallete.orange + + let layout = HStack(spacing: 8) { + view1 + view2 + view3 + view4 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testWithRedHavingHighestLayoutPriority() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UIView() + view2.backgroundColor = ColorPallete.red + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let view4 = UIView() + view4.backgroundColor = ColorPallete.orange + + let layout = HStack(spacing: 8) { + view1 + view2 + .layoutPriority(1) + view3 + view4 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testWithBlueHavingHighestLayoutPriority() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UIView() + view2.backgroundColor = ColorPallete.red + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let view4 = UIView() + view4.backgroundColor = ColorPallete.orange + + let layout = HStack(spacing: 8) { + view1 + view2 + view3 + .layoutPriority(1) + view4 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testWithOrangeHavingHighestLayoutPriority() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UIView() + view2.backgroundColor = ColorPallete.red + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let view4 = UIView() + view4.backgroundColor = ColorPallete.orange + + let layout = HStack(spacing: 8) { + view1 + view2 + view3 + view4 + .layoutPriority(1) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testWithRedAndOrangeHavingHighestLayoutPriority() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UIView() + view2.backgroundColor = ColorPallete.red + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let view4 = UIView() + view4.backgroundColor = ColorPallete.orange + + let layout = HStack(spacing: 8) { + view1 + view2 + .layoutPriority(1) + view3 + view4 + .layoutPriority(1) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } + + func testWithTextHavingHighestLayoutPriority() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UIView() + view2.backgroundColor = ColorPallete.red + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let view4 = UIView() + view4.backgroundColor = ColorPallete.orange + + let layout = HStack(spacing: 8) { + view1 + .layoutPriority(1) + view2 + view3 + view4 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 320)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ListCellServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ListCellServerSnapshotTests.swift new file mode 100644 index 0000000..15f1db7 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ListCellServerSnapshotTests.swift @@ -0,0 +1,90 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class ListCellItemViewServerSnaposhTests: FBServerSnapshotTestCase { + + private func runTest( + title: String, + subtitle: String, + content: String, + time: String, + multiline: Bool = false + ) { + + let titleLabel = UILabel() + let subtitlLabel = UILabel() + let contentLabel = UILabel() + let timeLabel = UILabel() + let iconView = UIView() + iconView.backgroundColor = .black + contentLabel.numberOfLines = 2 + + titleLabel.text = title + subtitlLabel.text = subtitle + contentLabel.text = content + timeLabel.text = time + + if multiline { + titleLabel.numberOfLines = 0 + subtitlLabel.numberOfLines = 0 + contentLabel.numberOfLines = 0 + timeLabel.numberOfLines = 0 + } + + let layout = VStack(alignment: .leading) { + HStack { + titleLabel + Spacer() + timeLabel + Spacer(4) + iconView + .frame(width: 10, height: 10) + } + subtitlLabel + contentLabel + } + .padding(.horizontal, 8) + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 700)) + ) + } + + func testShortText() { + runTest( + title: "Title", + subtitle: "Subitle", + content: "Content", + time: "22:00" + ) + } + + func testLongText() { + runTest( + title: "Title Nulla non sem et tortor euismod ornare. Duis blandit porta. End!!!", + subtitle: "Subtitle Name Aenean nec consectetur massa. Pellentesque id rhoncus metus. Suspendisse tincidunt. End!!!", + content: "Content Headline Integer pulvinar mollis ipsum, vel condimentum velit efficitur id. End!!!", + time: "22:00" + ) + } + + func testLongTextMultiline() { + runTest( + title: "Title Nulla non sem et tortor euismod ornare. Duis blandit porta. End!!!", + subtitle: "Subtitle Name Aenean nec consectetur massa. Pellentesque id rhoncus metus. Suspendisse tincidunt. End!!!", + content: "Content Headline Integer pulvinar mollis ipsum, vel condimentum velit efficitur id. End!!!", + time: "22:00", + multiline: true + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedStacksServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedStacksServerSnapshotTests.swift new file mode 100644 index 0000000..dfbc740 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedStacksServerSnapshotTests.swift @@ -0,0 +1,216 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class NestedStacksServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTest(on views: (UIView, UIView?)) { + takeSnapshot( + with: + HStack { + VStack { + VStack { + HStack { + HStack { + views.0 + if let view = views.1 { + view + } + } + } + } + } + }, + in: .proposed(CGSize(width: 300, height: 100)) + ) + } + + func testTwoLabelsInHStack() { + let view1 = UILabel() + view1.text = "Label 1" + + let view2 = UILabel() + view2.text = "Label 2" + + runTest(on: (view1, view2)) + } + + func testTwoUIViews() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUIScrollView() { + let view1 = UIScrollView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIScrollView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUITextField() { + let view1 = UITextField() + view1.backgroundColor = ColorPallete.blue + + let view2 = UITextField() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, view2)) + } + + func testTwoUIButton() { + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + + let view2 = UIButton(type: .system) + view2.setTitle("Button 2", for: .normal) + + runTest(on: (view1, view2)) + } + + func testTwoUIImageView() { + let view1 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.blue, size: CGSize(width: 40, height: 40))) + let view2 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.yellow, size: CGSize(width: 40, height: 40))) + + runTest(on: (view1, view2)) + } + + func testSingleLabelInHStack() { + let view1 = UILabel() + view1.text = "Label 1" + + runTest(on: (view1, nil)) + } + + func testSingleUIView() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + runTest(on: (view1, nil)) + } + + func testSingleUISlider() { + let view1 = UISlider() + view1.backgroundColor = ColorPallete.blue + + runTest(on: (view1, nil)) + } + + func testSingleUIScrollView() { + let view1 = UIScrollView() + view1.backgroundColor = ColorPallete.blue + + runTest(on: (view1, nil)) + } + + func testSingleUITextField() { + let view1 = UITextField() + view1.backgroundColor = ColorPallete.blue + + runTest(on: (view1, nil)) + } + + func testSingleUIButton() { + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + + runTest(on: (view1, nil)) + } + + func testSingleUIImageView() { + let view1 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.blue, size: CGSize(width: 40, height: 40))) + + runTest(on: (view1, nil)) + } + + func testResizableView() { + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + + takeSnapshot( + with: + HStack { + VStack { + VStack { + HStack { + HStack { + view1 + .resizable() + } + } + } + } + }, + in: .proposed(CGSize(width: 300, height: 100)) + ) + } + + func testResizableViewInFrame() { + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + + takeSnapshot( + with: + HStack { + VStack { + VStack { + HStack { + HStack { + view1 + .resizable() + .frame(width: 50, height: 50) + } + } + } + } + }, + in: .proposed(CGSize(width: 300, height: 100)) + ) + } + + func testResizableViewInFrameWithAnotherView() { + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + + let view2 = UIButton(type: .system) + view2.setTitle("Button 2", for: .normal) + + takeSnapshot( + with: + HStack { + VStack { + view1 + VStack { + HStack { + EmptyLayout() + HStack { + view2 + .resizable() + .frame(width: 50, height: 50) + } + } + } + } + }, + in: .proposed(CGSize(width: 300, height: 100)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedViewsWithIntrinsizeSizeRangeImplementationServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedViewsWithIntrinsizeSizeRangeImplementationServerSnapshotTests.swift new file mode 100644 index 0000000..856048c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedViewsWithIntrinsizeSizeRangeImplementationServerSnapshotTests.swift @@ -0,0 +1,134 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +private final class QuickIntrinsicSizeRangeView: UIView { + + var layout: Layout = EmptyLayout() + + override func layoutSubviews() { + super.layoutSubviews() + layout.applyFrame(bounds) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return layout.sizeThatFits(size) + } +} + +@MainActor +class NestedViewsWithIntrinsizeSizeRangeImplementationServerSnapshotTests: FBServerSnapshotTestCase { + + func testWithNestedViewsThaContainsLayoutWithHStacks() { + + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let parentView1 = QuickIntrinsicSizeRangeView() + parentView1.layout = HStack { + view1 + } + parentView1.addSubview(view1) + + let label1 = UILabel() + label1.text = "Nam id n" + label1.numberOfLines = 0 + + let parentView2 = QuickIntrinsicSizeRangeView() + parentView2.layout = HStack { + label1 + } + parentView2.addSubview(label1) + + let layout = HStack { + parentView1 + Spacer(8) + parentView2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testWithNestedViewsThaContainsLayoutWithZStacks() { + + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let parentView1 = QuickIntrinsicSizeRangeView() + parentView1.layout = ZStack { + view1 + } + parentView1.addSubview(view1) + + let label1 = UILabel() + label1.text = "Nam id n" + label1.numberOfLines = 0 + + let parentView2 = QuickIntrinsicSizeRangeView() + parentView2.layout = ZStack { + label1 + } + parentView2.addSubview(label1) + + let layout = HStack { + parentView1 + Spacer(8) + parentView2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testWithNestedViewsWithHStackAndSpacer() { + + let label1 = UILabel() + label1.text = "1" + + let label2 = UILabel() + label2.text = "1" + + let parentView1 = QuickIntrinsicSizeRangeView() + parentView1.layout = HStack { + label1 + Spacer() + label2 + } + parentView1.addSubview(label1) + parentView1.addSubview(label2) + parentView1.backgroundColor = ColorPallete.blue + + let label3 = UILabel() + label3.text = "Nam id n" + label3.numberOfLines = 0 + + let parentView2 = QuickIntrinsicSizeRangeView() + parentView2.layout = HStack { + label3 + } + parentView2.addSubview(label3) + + let layout = HStack { + parentView1 + Spacer(8) + parentView2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedViewsWithoutIntrinsizeSizeRangeImplementationServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedViewsWithoutIntrinsizeSizeRangeImplementationServerSnapshotTests.swift new file mode 100644 index 0000000..c0b8985 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NestedViewsWithoutIntrinsizeSizeRangeImplementationServerSnapshotTests.swift @@ -0,0 +1,155 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +private final class QuickIntrinsicSizeRangeNotProvidedView: UIView { + + var layout: Layout = EmptyLayout() + + override func layoutSubviews() { + super.layoutSubviews() + layout.applyFrame(bounds) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return layout.sizeThatFits(size) + } +} + +@MainActor +class NestedViewsWithoutIntrinsizeSizeRangeImplementationServerSnapshotTests: FBServerSnapshotTestCase { + + /// The reference layout. + func testWithoutNestedViews() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let label1 = UILabel() + label1.text = "Nam id n" + label1.numberOfLines = 0 + + let layout = HStack { + view1 + Spacer(8) + label1 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testWithNestedViewsThaContainsLayoutWithHStacks() { + + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let parentView1 = QuickIntrinsicSizeRangeNotProvidedView() + parentView1.layout = HStack { + view1 + } + parentView1.addSubview(view1) + + let label1 = UILabel() + label1.text = "Nam id n" + label1.numberOfLines = 0 + + let parentView2 = QuickIntrinsicSizeRangeNotProvidedView() + parentView2.layout = HStack { + label1 + } + parentView2.addSubview(label1) + + let layout = HStack { + parentView1 + Spacer(8) + parentView2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testWithNestedViewsThaContainsLayoutWithZStacks() { + + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let parentView1 = QuickIntrinsicSizeRangeNotProvidedView() + parentView1.layout = ZStack { + view1 + } + parentView1.addSubview(view1) + + let label1 = UILabel() + label1.text = "Nam id n" + label1.numberOfLines = 0 + + let parentView2 = QuickIntrinsicSizeRangeNotProvidedView() + parentView2.layout = ZStack { + label1 + } + parentView2.addSubview(label1) + + let layout = HStack { + parentView1 + Spacer(8) + parentView2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testWithNestedViewsWithHStackAndSpacer() { + + let label1 = UILabel() + label1.text = "1" + + let label2 = UILabel() + label2.text = "1" + + let parentView1 = QuickIntrinsicSizeRangeNotProvidedView() + parentView1.layout = HStack { + label1 + Spacer() + label2 + } + parentView1.addSubview(label1) + parentView1.addSubview(label2) + parentView1.backgroundColor = ColorPallete.blue + + let label3 = UILabel() + label3.text = "Nam id n" + label3.numberOfLines = 0 + + let parentView2 = QuickIntrinsicSizeRangeNotProvidedView() + parentView2.layout = HStack { + label3 + } + parentView2.addSubview(label3) + + let layout = HStack { + parentView1 + Spacer(8) + parentView2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NewsFeedItemWithFlatHeirarchyViewServerSnaposhTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NewsFeedItemWithFlatHeirarchyViewServerSnaposhTests.swift new file mode 100644 index 0000000..90f4c50 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/NewsFeedItemWithFlatHeirarchyViewServerSnaposhTests.swift @@ -0,0 +1,256 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class NewsFeedItemWithFlatHeirarchyViewServerSnaposhTests: FBServerSnapshotTestCase { + + private func runTest(content: TestNewsFeedView.Content) { + let view = TestNewsFeedView() + view.prepare() + view.setContent(content) + takeSnapshot( + of: view, + in: .proposed(CGSize(width: 320, height: 700)) + ) + } + + private func runMultiScreenTest(content: TestNewsFeedView.Content, font: (UITraitCollection) -> UIFont) { + makeMultipleViewSnapshot( + viewFactory: { contentSizeCategory in + let traitCollection = UITraitCollection(preferredContentSizeCategory: contentSizeCategory) + let view = TestNewsFeedView() + view.prepare() + view.setContent(content) + view.setFont(font(traitCollection)) + view.layer.borderColor = UIColor.black.cgColor + view.layer.borderWidth = 2 + return view + }, + configuration: SnapshotConfiguration( + screenSizes: [.iPhoneSmall, .iPhoneMedium, .iPhoneLarge], + preferredContentSizeCategories: [.extraSmall, .small, .medium, .large, .extraExtraExtraLarge], + sizingStrategy: [.assign, .measureVerticalIntrinsicSize(clampWithScreenSize: true), .measureVerticalIntrinsicSize(clampWithScreenSize: false)] + ) + ) + } + + func testShortText() { + let content = TestNewsFeedView.Content( + actionText: "Action", + posterName: "Poster Name", + posterHeadline: "Poster Headline", + posterTime: "Poster Time", + posterComment: "Poster Comment", + contentTitle: "Content Title", + contentDomain: "Content Domain", + actorComment: "Actor Comment", + numberOfLines: 1 + ) + runTest(content: content) + } + + func testLongText() { + let content = TestNewsFeedView.Content( + actionText: "Action Nulla non sem et tortor euismod ornare. Duis blandit porta. End!!!", + posterName: "Poster Name Aenean nec consectetur massa. Pellentesque id rhoncus metus. Suspendisse tincidunt. End!!!", + posterHeadline: "Poster Headline Integer pulvinar mollis ipsum, vel condimentum velit efficitur id. End!!!", + posterTime: "Poster Time Morbi laoreet, augue nec consequat elementum, arcu risus facilisis odio. End!!!", + posterComment: "Poster Comment Cras vulputate justo arcu, ac varius nunc tempus suscipit. End!!!", + contentTitle: "Content Title Nulla sed iaculis libero. End!!!", + contentDomain: "Content Domain Nam vitae neque quis mi rhoncus dictum eu ut tellus. End!!!", + actorComment: "Actor Comment Suspendisse eget posuere tortor. Pellentesque et mi mauris. Orci varius. End!!!", + numberOfLines: 1 + ) + runTest(content: content) + } + + func testLongTextMultiline() { + let content = TestNewsFeedView.Content( + actionText: "Action Nulla non sem et tortor euismod ornare. Duis blandit porta. End!!!", + posterName: "Poster Aenean nec consectetur massa. Pellentesque id rhoncus metus. Suspendisse tincidunt. End!!!", + posterHeadline: "Poster Headline Integer pulvinar mollis ipsum, vel condimentum velit efficitur id. End!!", + posterTime: "Poster Time Morbi laoreet, augue nec consequat elementum, arcu risus facilisis odio. End!!!", + posterComment: "Poster Comment Cras vulputate justo arcu, ac varius nunc tempus suscipit. End!!!", + contentTitle: "Content Title Nulla sed iaculis libero. End!!!", + contentDomain: "Content Domain Nam vitae neque quis mi rhoncus dictum eu ut tellus. End!!!", + actorComment: "Actor Comment Suspendisse eget posuere tortor. Pellentesque et mi mauris. Orci varius. End!!!", + numberOfLines: 0 + ) + runTest(content: content) + } + + func test_GetStarted() { + let content = TestNewsFeedView.Content( + actionText: "Action Nulla non sem et tortor euismod ornare. Duis blandit porta. End!!!", + posterName: "Poster Aenean nec consectetur massa. Pellentesque id rhoncus metus. Suspendisse tincidunt. End!!!", + posterHeadline: "Poster Headline Integer pulvinar mollis ipsum, vel condimentum velit efficitur id. End!!", + posterTime: "Poster Time Morbi laoreet, augue nec consequat elementum, arcu risus facilisis odio. End!!!", + posterComment: "Poster Comment Cras vulputate justo arcu, ac varius nunc tempus suscipit. End!!!", + contentTitle: "Content Title Nulla sed iaculis libero. End!!!", + contentDomain: "Content Domain Nam vitae neque quis mi rhoncus dictum eu ut tellus. End!!!", + actorComment: "Actor Comment Suspendisse eget posuere tortor. Pellentesque et mi mauris. Orci varius. End!!!", + numberOfLines: 0 + ) + runMultiScreenTest( + content: content, + font: { traitCollection in + UIFont.preferredFont(forTextStyle: .body, compatibleWith: traitCollection) + }) + } +} + +private final class TestNewsFeedView: UIView { + + struct Content { + let actionText: String + let posterName: String + let posterHeadline: String + let posterTime: String + let posterComment: String + let contentTitle: String + let contentDomain: String + let actorComment: String + let numberOfLines: Int + } + + let actionLabel = UILabel() + let optionsLabel = UILabel() + let posterImageView = TestPlaceholderView(lineColor: UIColor.black) + let posterNameLabel = UILabel() + let posterHeadlineLabel = UILabel() + let posterTimeLabel = UILabel() + let posterCommentLabel = UILabel() + let contentImageView = TestPlaceholderView(lineColor: UIColor.black) + let contentTitleLabel = UILabel() + let contentDomainLabel = UILabel() + let likeLabel = UILabel() + let commentLabel = UILabel() + let shareLabel = UILabel() + let actorImageView = TestPlaceholderView(lineColor: UIColor.black) + let actorCommentLabel = UILabel() + + func prepare() { + self.backgroundColor = .white + contentImageView.backgroundColor = .black + posterImageView.backgroundColor = .black + actorImageView.backgroundColor = .black + + self.addSubview(actionLabel) + self.addSubview(optionsLabel) + self.addSubview(posterImageView) + self.addSubview(posterNameLabel) + self.addSubview(posterHeadlineLabel) + self.addSubview(posterTimeLabel) + self.addSubview(posterCommentLabel) + self.addSubview(contentImageView) + self.addSubview(contentTitleLabel) + self.addSubview(contentDomainLabel) + self.addSubview(likeLabel) + self.addSubview(commentLabel) + self.addSubview(shareLabel) + self.addSubview(actorImageView) + self.addSubview(actorCommentLabel) + } + + func setFont(_ font: UIFont) { + actionLabel.font = font + posterNameLabel.font = font + posterHeadlineLabel.font = font + posterTimeLabel.font = font + posterCommentLabel.font = font + contentTitleLabel.font = font + contentDomainLabel.font = font + actorCommentLabel.font = font + } + + func setContent(_ content: Content) { + optionsLabel.text = "..." + likeLabel.text = "Like" + commentLabel.text = "Comment" + shareLabel.text = "Share" + + actionLabel.text = content.actionText + posterNameLabel.text = content.posterName + posterHeadlineLabel.text = content.posterHeadline + posterTimeLabel.text = content.posterTime + posterCommentLabel.text = content.posterComment + contentTitleLabel.text = content.contentTitle + contentDomainLabel.text = content.contentDomain + actorCommentLabel.text = content.actorComment + + actionLabel.numberOfLines = content.numberOfLines + posterNameLabel.numberOfLines = content.numberOfLines + posterHeadlineLabel.numberOfLines = content.numberOfLines + posterTimeLabel.numberOfLines = content.numberOfLines + posterCommentLabel.numberOfLines = content.numberOfLines + contentTitleLabel.numberOfLines = content.numberOfLines + contentDomainLabel.numberOfLines = content.numberOfLines + actorCommentLabel.numberOfLines = content.numberOfLines + } + + @LayoutBuilder + func layout() -> Layout { + VStack(alignment: .leading) { + + HStack { + actionLabel + Spacer() + optionsLabel + } + + HStack { + posterImageView + .resizable() + .frame(width: 50, height: 50) + + Spacer(2) + + VStack(alignment: .leading) { + posterNameLabel + posterHeadlineLabel + posterTimeLabel + } + } + + posterCommentLabel + contentImageView + .resizable() + .aspectRatio(CGSize(width: 350, height: 200)) + + contentTitleLabel + contentDomainLabel + HStack { + likeLabel + Spacer() + commentLabel + Spacer() + shareLabel + } + HStack { + actorImageView + .resizable() + .frame(width: 50, height: 50) + Spacer(4) + actorCommentLabel + } + } + .padding(.horizontal, 8) + } + + override func layoutSubviews() { + super.layoutSubviews() + layout().applyFrame(bounds) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return layout().sizeThatFits(size) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/OffsetServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/OffsetServerSnapshotTests.swift new file mode 100644 index 0000000..f45fd8c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/OffsetServerSnapshotTests.swift @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import QuickLayoutBridge +import XCTest + +@MainActor +class OffsetServerSnapshotTests: FBServerSnapshotTestCase { + + func testOffset() { + + let size = 20 + + let firstView = ViewWithSize(customSize: CGSize(width: size, height: size)) + firstView.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view2.backgroundColor = ColorPallete.blue + + let view3 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view3.backgroundColor = ColorPallete.blue + + let view4 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view4.backgroundColor = ColorPallete.blue + + let view5 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view5.backgroundColor = ColorPallete.blue + + let view6 = ViewWithSize(customSize: CGSize(width: size, height: size)) + view6.backgroundColor = ColorPallete.blue + + let lastView = ViewWithSize(customSize: CGSize(width: size, height: size)) + lastView.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 20) { + firstView + view2 + .offset(x: -10, y: 0) + view3 + .offset(x: 10, y: 0) + view4 + .offset(x: 0, y: 10) + view5 + .offset(x: 0, y: -10) + view6 + .offset(x: .infinity, y: .infinity) // should be ignored + lastView + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 300, height: 300)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/PaddingServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/PaddingServerSnapshotTests.swift new file mode 100644 index 0000000..1c28974 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/PaddingServerSnapshotTests.swift @@ -0,0 +1,197 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class PaddingServerSnaposhTests: FBServerSnapshotTestCase { + + func testPaddingAll() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding(.top, 20) + .padding(.leading, 10) + .padding(.bottom, 80) + .padding(.trailing, 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingTop() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding(.top, 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingLeading() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding(.leading, 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingTrailing() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding(.trailing, 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingBottom() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding(.bottom, 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingHorizontal() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding(.horizontal, 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingVertical() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding(.vertical, 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingTLTB() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding([.top, .leading, .trailing, .bottom], 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingLeadingTrailing() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding([.leading, .trailing], 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } + + func testPaddingTopBottom() { + + let size = CGSize(width: 100, height: 100) + + let view1 = UIImageView(image: generateTestImage(with: "1", size: size)) + + let layout = ZStack { + view1 + .padding([.top, .bottom], 20) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)), + containerBackground: ColorPallete.yellow + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/QuicklyInvalidSizesServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/QuicklyInvalidSizesServerSnapshotTests.swift new file mode 100644 index 0000000..7d5dcc2 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/QuicklyInvalidSizesServerSnapshotTests.swift @@ -0,0 +1,497 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class QuicklyInvalidSizesServerSnapshotTests: FBServerSnapshotTestCase { + + func testProposeZero() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize.zero) + ) + } + + func testProposeInfinity() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: CGFloat.infinity, height: .infinity)) + ) + } + + func testProposeGreatestFiniteValue() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: CGFloat.greatestFiniteMagnitude, height: .greatestFiniteMagnitude)) + ) + } + + func testProposeNan() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: CGFloat.nan, height: .nan)) + ) + } + + func testProposeMinusOne() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: -1.0, height: -1.0)) + ) + } + + func testProposeMinus100() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: -100.0, height: -100.0)) + ) + } + + func testViewReturnMinus1() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = View() + view2.mockedSize = CGSize(width: -1.0, height: -1.0) + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 10, height: 10)) + ) + } + + func testViewReturnNan() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = View() + view2.mockedSize = CGSize(width: CGFloat.nan, height: .nan) + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 10, height: 10)) + ) + } + + func testViewReturnInVStack() { + let view2 = View() + view2.mockedSize = CGSize(width: CGFloat.nan, height: .nan) + view2.backgroundColor = ColorPallete.blue + + let layout = VStack { + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 10, height: 10)) + ) + } + + func testViewReturnsInfinity() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = View() + view2.mockedSize = CGSize(width: CGFloat.infinity, height: .infinity) + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 10, height: 10)) + ) + } + + func testViewReturns100() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = View() + view2.mockedSize = CGSize(width: 100, height: 100) + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.orange + + let layout = VStack { + ZStack { + view1 + .padding(1) + view2 + .aspectRatio(CGSize(width: 1, height: 1)) + view3 + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 10, height: 10)) + ) + } + + func testAspectRatio() { + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.yellow + + let view4 = UIView() + view4.backgroundColor = ColorPallete.red + + let view5 = UIView() + view5.backgroundColor = ColorPallete.blue + + let view6 = UIView() + view6.backgroundColor = ColorPallete.red + + let view7 = UIView() + view7.backgroundColor = ColorPallete.red + + let layout = VStack { + view0 + .aspectRatio(CGSize(width: 1.0, height: .infinity)) + .frame(width: 10, height: 10) + view1 + .aspectRatio(CGSize.zero) + .frame(width: 10, height: 10) + view2 + .aspectRatio(CGSize(width: -1, height: 0)) + .frame(width: 10, height: 10) + view3 + .aspectRatio(CGSize(width: 0, height: 1), contentMode: .fill) + .frame(width: 10, height: 10) + view4 + .aspectRatio(CGSize(width: 1, height: -1), contentMode: .fill) + .frame(width: 10, height: 10) + view5 + .aspectRatio(CGSize(width: 1.0, height: .nan), contentMode: .fill) + .frame(width: 10, height: 10) + view6 + .aspectRatio(CGSize(width: 1.0, height: 1.0), contentMode: .fit) + .frame(width: 10, height: 0) + view7 + .aspectRatio(CGSize(width: 1.0, height: 1.0), contentMode: .fit) + .frame(width: 10, height: .infinity) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 100, height: 100)) + ) + } + + func testFrame() { + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.yellow + + let view4 = UIView() + view4.backgroundColor = ColorPallete.red + + let view5 = UIView() + view5.backgroundColor = ColorPallete.blue + + let layout = VStack { + view0 + .frame(width: 10.0, height: .nan) + view1 + .frame(width: .nan, height: 10) + view2 + .frame(width: .infinity, height: 10) + view4 + .frame(width: -10.0, height: 10) + view5 + .frame(width: 10.0, height: -10) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 100, height: 100)) + ) + } + + func testPadding() { + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let layout = VStack { + view0 + .padding(-10) + view1 + .padding(.infinity) + view2 + .padding(.nan) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 100, height: 100)) + ) + } + + func testStacks() { + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let view1 = UIView() + view1.backgroundColor = ColorPallete.red + + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let view3 = UIView() + view3.backgroundColor = ColorPallete.yellow + + let view4 = UIView() + view4.backgroundColor = ColorPallete.red + + let view5 = UIView() + view5.backgroundColor = ColorPallete.blue + + let layout = VStack { + HStack(spacing: .nan) { + view0 + .frame(width: 20, height: 10) + view1 + .frame(width: 20, height: 10) + } + HStack(spacing: .infinity) { + view2 + .frame(width: 20, height: 10) + view3 + .frame(width: 20, height: 10) + } + HStack(spacing: -10) { + view4 + .frame(width: 20, height: 10) + view5 + .frame(width: 20, height: 10) + } + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 100, height: 100)) + ) + } + + func testApplyFrameWithNan() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.layer.borderColor = UIColor.black.cgColor + view2.layer.borderWidth = 1 + view2.backgroundColor = .clear + + let layout = ZStack(alignment: .leading) { + view1 + } + + let targetView = UIView() + targetView.addSubview(view1) + targetView.addSubview(view2) + targetView.frame.size = CGSize(width: 200, height: 200) + + layout.applyFrame(CGRect(origin: .zero, size: CGSize(width: CGFloat.nan, height: CGFloat.nan))) + + let backgroundView = UIView() + backgroundView.frame.size = CGSize(width: 200, height: 200) + backgroundView.backgroundColor = .clear + backgroundView.addSubview(targetView) + targetView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] + FBTakeSnapshotOfViewAfterScreenUpdates(backgroundView, nil) + } +} + +private class View: UIView { + + var mockedSize: CGSize = .zero + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return mockedSize + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/QuicklyTestHelpers.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/QuicklyTestHelpers.swift new file mode 100644 index 0000000..9f8864d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/QuicklyTestHelpers.swift @@ -0,0 +1,237 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import Foundation +import QuickLayoutBridge + +struct ColorPallete { + static let blue = UIColor(hex: 0x4793AF) + static let yellow = UIColor(hex: 0xFFC470) + static let orange = UIColor(hex: 0xDD5746) + static let red = UIColor(hex: 0x8B322C) +} + +enum SizeType { + case proposed(_ size: CGSize) + case exact(_ size: CGSize) +} + +func takeSnapshot( + with layout: Layout, + in sizeType: SizeType, + alignment: Alignment? = nil, + identifier: String? = nil, + containerBackground: UIColor? = nil, + layoutDirection: LayoutDirection? = nil +) { + let targetView = TestView() + targetView.backgroundColor = containerBackground + layout.views().forEach { targetView.addSubview($0) } + targetView.layout = layout + targetView.layoutDirection = layoutDirection + targetView.alignment = alignment + switch sizeType { + case .exact(let size): targetView.frame.size = size + case .proposed(let size): targetView.frame.size = targetView.sizeThatFits(size) + } + + let backgroundView = UIView() + backgroundView.bounds = targetView.bounds + backgroundView.backgroundColor = .clear + backgroundView.addSubview(targetView) + targetView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] + FBTakeSnapshotOfViewAfterScreenUpdates(backgroundView, nil) +} + +func takeSnapshot(of targetView: UIView, in sizeType: SizeType, identifier: String? = nil) { + + switch sizeType { + case .exact(let size): targetView.frame.size = size + case .proposed(let size): targetView.frame.size = targetView.sizeThatFits(size) + } + + let backgroundView = UIView() + backgroundView.bounds = targetView.bounds + backgroundView.backgroundColor = .clear + backgroundView.addSubview(targetView) + targetView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] + FBTakeSnapshotOfViewAfterScreenUpdates(backgroundView, nil) +} + +final class TestView: UIView { + + var layout: Layout = EmptyLayout() + var alignment: Alignment? + var layoutDirection: LayoutDirection? + + override func layoutSubviews() { + super.layoutSubviews() + if let alignment, let layoutDirection { + layout.applyFrame(bounds, alignment: alignment, layoutDirection: layoutDirection) + } else if let alignment { + layout.applyFrame(bounds, alignment: alignment) + } else if let layoutDirection { + layout.applyFrame(bounds, alignment: .center, layoutDirection: layoutDirection) + } else { + layout.applyFrame(bounds) + } + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return layout.sizeThatFits(size) + } +} + +class ViewWithSize: UIView { + + let customSize: CGSize + var proposedSizes = [CGSize]() + + init(customSize: CGSize) { + self.customSize = customSize + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + proposedSizes.append(size) + return customSize + } +} + +func generateTestImage(with text: String, size: CGSize, backgroundColor: UIColor = ColorPallete.blue) -> UIImage? { + assert(Thread.isMainThread) + + let frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) + let label = UILabel(frame: frame) + label.textAlignment = .center + label.backgroundColor = backgroundColor + label.textColor = .white + label.font = UIFont.monospacedDigitSystemFont(ofSize: 30, weight: .regular) + label.text = text + + let renderer = UIGraphicsImageRenderer(size: frame.size) + let image = renderer.image { (context) in + label.layer.render(in: context.cgContext) + } + return image +} + +private extension UIColor { + convenience init(red: Int, green: Int, blue: Int) { + assert(red >= 0 && red <= 255, "Invalid red component") + assert(green >= 0 && green <= 255, "Invalid green component") + assert(blue >= 0 && blue <= 255, "Invalid blue component") + + self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) + } + + convenience init(hex: Int) { + self.init( + red: (hex >> 16) & 0xFF, + green: (hex >> 8) & 0xFF, + blue: hex & 0xFF + ) + } +} + +class TestPlaceholderView: UIView { + + private let lineColor: UIColor + private let lineWidth: CGFloat + private let intrinsicSize: CGSize? + private let fillColor: UIColor + + init(lineColor: UIColor = UIColor.black, lineWidth: CGFloat = 2, fillColor: UIColor = .white, intrinsicSize: CGSize? = nil) { + self.lineColor = lineColor + self.intrinsicSize = intrinsicSize + self.lineWidth = lineWidth + self.fillColor = fillColor + super.init(frame: .zero) + layer.borderColor = lineColor.cgColor + layer.borderWidth = lineWidth + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // Fully flexible if no intrinsic size is set. + override func sizeThatFits(_ size: CGSize) -> CGSize { + return intrinsicSize ?? size + } + + override func draw(_ rect: CGRect) { + fillColor.setFill() + + UIRectFill(rect) + + lineColor.setStroke() + + let linePath = UIBezierPath() + linePath.lineWidth = lineWidth + // First line from top left to bottom right + linePath.move(to: CGPoint.zero) + linePath.addLine(to: CGPoint(x: rect.width, y: rect.height)) + linePath.stroke() + // Second line from top right to bottom left + linePath.move(to: CGPoint(x: rect.width, y: 0)) + linePath.addLine(to: CGPoint(x: 0, y: rect.height)) + linePath.stroke() + } +} + +class ColorView: UIView { + + private let text: String? + + init(_ color: UIColor, text: String? = nil) { + self.text = text + super.init(frame: .zero) + self.backgroundColor = color + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + + if let text { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + + let attributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.systemFont(ofSize: 16), + .paragraphStyle: paragraphStyle, + .foregroundColor: UIColor.black, + ] + + let attributedString = NSAttributedString(string: text, attributes: attributes) + let textRect = CGRect(x: 0, y: (rect.height - attributedString.size().height) / 2, width: rect.width, height: attributedString.size().height) + attributedString.draw(in: textRect) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class BorderView: UIView { + init() { + super.init(frame: .zero) + self.layer.borderColor = UIColor.black.cgColor + self.layer.borderWidth = 1 + self.backgroundColor = UIColor.clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ResizableServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ResizableServerSnapshotTests.swift new file mode 100644 index 0000000..e741a16 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ResizableServerSnapshotTests.swift @@ -0,0 +1,115 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class ResizableServerSnaposhTests: FBServerSnapshotTestCase { + + func testResizableWithUIImage() { + + let view1 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.blue, size: CGSize(width: 50, height: 50))) + let view2 = UIImageView(image: FBTestImageGenerator.image(with: ColorPallete.yellow, size: CGSize(width: 50, height: 50))) + + let layout = HStack { + view1 + view2 + .resizable() + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testResizableWithUIButton() { + + let view1 = UIButton(type: .system) + view1.setTitle("Button 1", for: .normal) + view1.setTitleColor(.white, for: .normal) + view1.backgroundColor = ColorPallete.blue + + let view2 = UIButton(type: .system) + view2.setTitle("Button 2", for: .normal) + view2.setTitleColor(.white, for: .normal) + view2.backgroundColor = ColorPallete.yellow + + let layout = HStack { + view1 + view2 + .resizable() + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testResizableWithCustomView() { + + let view1 = ViewWithSize(customSize: CGSize(width: 50, height: 50)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: 50, height: 50)) + view2.backgroundColor = ColorPallete.yellow + + let layout = HStack { + view1 + view2 + .resizable() + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testResizableWithCustomView_resizeOnlyVertically() { + + let view1 = ViewWithSize(customSize: CGSize(width: 50, height: 50)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: 50, height: 50)) + view2.backgroundColor = ColorPallete.yellow + + let layout = HStack { + view1 + view2 + .resizable(axis: .vertical) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } + + func testResizableWithCustomView_resizeOnlyHorizontally() { + + let view1 = ViewWithSize(customSize: CGSize(width: 50, height: 50)) + view1.backgroundColor = ColorPallete.blue + + let view2 = ViewWithSize(customSize: CGSize(width: 50, height: 30)) + view2.backgroundColor = ColorPallete.yellow + + let layout = HStack { + view1 + view2 + .resizable(axis: .horizontal) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 100)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/SingleViewServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/SingleViewServerSnapshotTests.swift new file mode 100644 index 0000000..194c5c9 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/SingleViewServerSnapshotTests.swift @@ -0,0 +1,58 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +private class SingleSubviewView: UIView { + + let label = UILabel() + + init(_ text: String) { + super.init(frame: .zero) + label.text = text + label.textColor = .white + self.addSubview(label) + } + + @LayoutBuilder + var body: any Layout { + label + } + + public required init?(coder: NSCoder) { + fatalError() + } + + override func layoutSubviews() { + super.layoutSubviews() + body.applyFrame(bounds) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return body.sizeThatFits(size) + } +} + +@MainActor +class SingleViewServerSnaposhTests: FBServerSnapshotTestCase { + + func testTestAViewWithSingleSubview() { + let view1 = SingleSubviewView("Ut enim dui") + view1.backgroundColor = ColorPallete.blue + + let layout = HStack { + view1 + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 200, height: 200)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/StacksWithSpacingSeverSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/StacksWithSpacingSeverSnapshotTests.swift new file mode 100644 index 0000000..9e82280 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/StacksWithSpacingSeverSnapshotTests.swift @@ -0,0 +1,574 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import QuickLayoutBridge + +@MainActor +class StacksWithSpacingSeverSnapshotTests: FBServerSnapshotTestCase { + + func testSpacersWithSpacing() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + + let view4 = UIView() + view4.backgroundColor = ColorPallete.red + + let layout = HStack(spacing: 10) { + view1 + .frame(width: 40, height: 40) + view2 + .frame(width: 40, height: 40) + Spacer(10) + view3 + .frame(width: 40, height: 40) + Spacer(10) + view4 + .frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testEmptyLayouts() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + EmptyLayout() + EmptyLayout() + EmptyLayout() + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSingleThreeEmptyLayouts() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + HStack(spacing: 10) { + EmptyLayout() + EmptyLayout() + EmptyLayout() + } + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testEmptyStack() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + HStack {} + VStack {} + ZStack {} + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksWithEmptyLayout() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + HStack { EmptyLayout() } + HStack { EmptyLayout() } + ZStack { EmptyLayout() } + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksWithTwoEmptyLayout() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + VStack { + EmptyLayout() + EmptyLayout() + } + ZStack { + EmptyLayout() + EmptyLayout() + } + HStack { + EmptyLayout() + EmptyLayout() + } + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksEmptyLayoutAndEmptyStack() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + VStack { + EmptyLayout() + VStack {} + } + VStack { + EmptyLayout() + VStack {} + } + VStack { + EmptyLayout() + ZStack {} + } + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksEmptyLayoutAndNonEmptyStack() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + VStack { + EmptyLayout() + HStack { + Spacer(10) + } + } + VStack { + EmptyLayout() + HStack { + Spacer(10) + } + } + VStack { + EmptyLayout() + HStack { + Spacer(10) + } + } + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testEmptyLayoutsAtTheEndAndStart() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + EmptyLayout() + EmptyLayout() + view1.frame(width: 40, height: 40) + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + EmptyLayout() + EmptyLayout() + EmptyLayout() + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSpacersWithSpacingAtTheEndAndStart() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + + let layout = HStack(spacing: 10) { + Spacer(10) + view1 + .frame(width: 40, height: 40) + view2 + .frame(width: 40, height: 40) + Spacer(10) + view3 + .frame(width: 40, height: 40) + Spacer(10) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testNestedStackWithZeroSpacer() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + VStack { + HStack { + Spacer(0) + } + } + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksWithZeroSpacer() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + Spacer(0) + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksWithZeroSpacerTwice() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + Spacer(0) + Spacer(0) + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksWithSpacer10() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 10) { + view1.frame(width: 40, height: 40) + Spacer(10) + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testStacksWithSpacer30() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 30) { + view1.frame(width: 40, height: 40) + Spacer(10) + Spacer(10) + Spacer(10) + view2.frame(width: 40, height: 40) + view3.frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSpacingWithFullyFlexibleElements() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 30) { + view1.frame(height: 40) + Spacer(10) + Spacer(10) + Spacer(10) + view2.frame(height: 40) + view3.frame(height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSpacingWithFullyFlexibleElementsWithoutSpacers() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 30) { + view1.frame(height: 40) + view2.frame(height: 40) + view3.frame(height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSpacingWithFullyFlexibleElementsWithEmptyLayout() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 30) { + view1.frame(height: 40) + EmptyLayout() + view2.frame(height: 40) + view3.frame(height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSpacingWithFullyFlexibleElementsWithEmptyLayoutAndSpacer() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 30) { + view1.frame(height: 40) + EmptyLayout() + Spacer(10) + view2.frame(height: 40) + view3.frame(height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSpacingWithFullyFlexibleElementsWithEmptyLayoutAndSpacer2() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 30) { + view1.frame(height: 40) + Spacer(10) + EmptyLayout() + view2.frame(height: 40) + view3.frame(height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testSpacingWithFullyFlexibleViewsAndSpacer() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.blue + + let layout = HStack(spacing: 30) { + view1.frame(height: 40) + Spacer(10) + Spacer() + view2.frame(height: 40) + view3.frame(height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/TestsInfra/TestSelfSizingScrollView.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/TestsInfra/TestSelfSizingScrollView.swift new file mode 100644 index 0000000..63940a9 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/TestsInfra/TestSelfSizingScrollView.swift @@ -0,0 +1,48 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge +import UIKit + +/// Vertically self-sizing scroll view that grows up to the content height. +/// It allows the parent to compress itself below the content's height but never grows larger than the content. +/// Same as FOASelfSizingScrollView. +public final class TestSelfSizingScrollView: UIScrollView { + public enum ScrollableAxis { + case vertical, horizontal + } + + public var scrollableAxis = ScrollableAxis.vertical + public var scrollableContentLayout: (() -> (Element & Layout)?)? + + override public func layoutSubviews() { + super.layoutSubviews() + guard let layout = scrollableContentLayout?() else { return } + + switch scrollableAxis { + case .vertical: + self.contentSize = layout.sizeThatFits(CGSize(width: bounds.width, height: .infinity)) + layout.applyFrame(CGRect(x: 0, y: 0, width: bounds.width, height: .infinity)) + case .horizontal: + self.contentSize = layout.sizeThatFits(CGSize(width: .infinity, height: bounds.height)) + layout.applyFrame(CGRect(x: 0, y: 0, width: .infinity, height: bounds.height)) + } + } + + override public func sizeThatFits(_ proposedSize: CGSize) -> CGSize { + guard let layout = scrollableContentLayout?() else { return proposedSize } + + switch scrollableAxis { + case .vertical: + let contentSize = layout.sizeThatFits(CGSize(width: proposedSize.width, height: .infinity)) + return CGSize(width: contentSize.width, height: min(proposedSize.height, contentSize.height)) + case .horizontal: + let contentSize = layout.sizeThatFits(CGSize(width: .infinity, height: proposedSize.height)) + return CGSize(width: min(proposedSize.width, contentSize.width), height: contentSize.height) + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/TestsInfra/ViewScreenSizeSnapshotTesting.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/TestsInfra/ViewScreenSizeSnapshotTesting.swift new file mode 100644 index 0000000..f87bec1 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/TestsInfra/ViewScreenSizeSnapshotTesting.swift @@ -0,0 +1,163 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import UIKit + +enum ScreenSize { + + /// iPhone SE, 375x667 + case iPhoneSmall + + /// iPhone 12, 12 Pro, 390x844 + case iPhoneMedium + + /// iPhone 12 Pro Max, 428x926 + case iPhoneLarge + + case custom(size: CGSize) + + var size: CGSize { + switch self { + case .iPhoneSmall: + return CGSize(width: 375.0, height: 667.0) + case .iPhoneMedium: + return CGSize(width: 390.0, height: 844.0) + case .iPhoneLarge: + return CGSize(width: 428.0, height: 926.0) + case .custom(let size): + return size + } + } + + var identifier: String { + switch self { + case .iPhoneSmall: + return "iPhoneSmall" + case .iPhoneMedium: + return "iPhoneMedium" + case .iPhoneLarge: + return "iPhoneLarge" + case .custom(let size): + return "Custom_w_\(size.width)_h_\(size.height)" + } + } +} + +enum SizingStrategy { + case assign + case measureVerticalIntrinsicSize(clampWithScreenSize: Bool) + + func makeIdentifier() -> String { + switch self { + case .assign: return "SizingStrategy_Assign" + case .measureVerticalIntrinsicSize(let clampWithScreenSize): + if clampWithScreenSize { + return "SizingStrategy_MeasureVerticalIntrinsicSizeWithClampingToScreenSize" + } else { + return "SizingStrategy_MeasureVerticalIntrinsicSize" + } + } + } +} + +struct SnapshotConfiguration { + let screenSizes: [ScreenSize] + let preferredContentSizeCategories: [UIContentSizeCategory] + let sizingStrategy: [SizingStrategy] +} + +struct FlatSnapshotConfiguration { + let screenSize: ScreenSize + let preferredContentSizeCategory: UIContentSizeCategory + let sizingStrategy: SizingStrategy + + func makeIdentifier() -> String { + return "iPhone_Portrait_Orientation_ScreenSize_" + screenSize.identifier + "_" + makeIdentifierFor(contentSizeCategory: preferredContentSizeCategory) + "_" + sizingStrategy.makeIdentifier() + } +} + +func makeMultipleViewSnapshot( + viewFactory: (UIContentSizeCategory) -> T, + configuration: SnapshotConfiguration +) { + + let flatConfigurations = configuration.screenSizes.flatMap { screenSize in + configuration.preferredContentSizeCategories.flatMap { contentSizeCategory in + configuration.sizingStrategy.map { sizingStrategy in + return FlatSnapshotConfiguration( + screenSize: screenSize, + preferredContentSizeCategory: contentSizeCategory, + sizingStrategy: sizingStrategy + ) + } + } + } + + flatConfigurations.forEach { flatConfiguration in + makeSingleViewSnapshot(viewFactory: viewFactory, flatConfiguration: flatConfiguration) + } +} + +func makeSingleViewSnapshot( + viewFactory: (UIContentSizeCategory) -> T, + flatConfiguration: FlatSnapshotConfiguration +) { + let screenSize = flatConfiguration.screenSize + let contentSizeCategory = flatConfiguration.preferredContentSizeCategory + let sizingStrategy = flatConfiguration.sizingStrategy + let identifier = flatConfiguration.makeIdentifier() + + let viewUnderTest = viewFactory(contentSizeCategory) + switch sizingStrategy { + case .assign: + viewUnderTest.frame = CGRect(origin: .zero, size: screenSize.size) + case .measureVerticalIntrinsicSize(let clampWithScreenSize): + let intrinsicSize = viewUnderTest.sizeThatFits(CGSize(width: screenSize.size.width, height: .infinity)) + let viewSize = CGSize(width: screenSize.size.width, height: clampWithScreenSize ? min(intrinsicSize.height, screenSize.size.height) : intrinsicSize.height) + viewUnderTest.frame = CGRect(origin: .zero, size: viewSize) + } + + viewUnderTest.setNeedsLayout() + viewUnderTest.layoutIfNeeded() + + if Bundle.main.bundleIdentifier == "com.meta.internal.uipreview" { + // let snapshotImage = FBRecordSnapshotWithView(viewUnderTest, true) ?? UIImage() + // let imageView = UIImageView(image: snapshotImage) + // imageView.backgroundColor = viewUnderTest.backgroundColor + // imageView.frame = CGRect(origin: .zero, size: snapshotImage.size) + // imageView.contentMode = .scaleAspectFit + + let scrollView = UIScrollView() + scrollView.alwaysBounceVertical = true + scrollView.alwaysBounceHorizontal = true + scrollView.contentSize = viewUnderTest.frame.size + scrollView.addSubview(viewUnderTest) + FBTakeSnapshotOfViewAfterScreenUpdates(scrollView, "Quickly_MultipleViewTests_" + identifier) + } else { + FBTakeSnapshotOfViewAfterScreenUpdates(viewUnderTest, "Quickly_MultipleViewTests_" + identifier) + } +} + +private func makeIdentifierFor(contentSizeCategory: UIContentSizeCategory) -> String { + switch contentSizeCategory { + case .unspecified: return "UIContentSizeCategory_Unspecified" + case .extraSmall: return "UIContentSizeCategory_ExtraSmall" + case .small: return "UIContentSizeCategory_Small" + case .medium: return "UIContentSizeCategory_Medium" + case .large: return "UIContentSizeCategory_Large" + case .extraLarge: return "UIContentSizeCategory_ExtraLarge" + case .extraExtraLarge: return "UIContentSizeCategory_ExtraExtraLarge" + case .extraExtraExtraLarge: return "UIContentSizeCategory_ExtraExtraExtraLarge" + case .accessibilityMedium: return "UIContentSizeCategory_AccessibilityMedium" + case .accessibilityLarge: return "UIContentSizeCategory_AccessibilityLarge" + case .accessibilityExtraLarge: return "UIContentSizeCategory_AccessibilityExtraLarge" + case .accessibilityExtraExtraLarge: return "UIContentSizeCategory_AccessibilityExtraExtraLarge" + case .accessibilityExtraExtraExtraLarge: return "UIContentSizeCategory_AccessibilityExtraExtraExtraLarge" + default: return "UIContentSizeCategory_Unknown" + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/UIButtonsServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/UIButtonsServerSnapshotTests.swift new file mode 100644 index 0000000..f8d616e --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/UIButtonsServerSnapshotTests.swift @@ -0,0 +1,122 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class UIButtonsServerSnapshotTests: FBServerSnapshotTestCase { + + func testButtonsWithDifferentTypes() { + + let button0 = UIButton() + button0.setTitleColor(.systemBlue, for: .normal) + button0.setTitleColor(.systemBlue.withAlphaComponent(0.7), for: .highlighted) + button0.setTitle("Button 0", for: .normal) + + let button1 = UIButton(type: .system) + button1.setTitle("Button 1", for: .normal) + + let button2 = UIButton(type: .custom) + button2.setTitleColor(.systemBlue, for: .normal) + button2.setTitleColor(.systemBlue.withAlphaComponent(0.7), for: .highlighted) + button2.setTitle("Button 2", for: .normal) + + let button3 = UIButton(type: .detailDisclosure) + button3.setTitle("Button 3", for: .normal) + + let button4 = UIButton(type: .infoDark) + button4.setTitle("Button 4", for: .normal) + + let button5 = UIButton(type: .infoLight) + button5.setTitle("Button 5", for: .normal) + + let button6 = UIButton(type: .contactAdd) + button6.setTitle("Button 6", for: .normal) + + let layout = VStack(spacing: 8) { + button0 + button1 + button2 + button3 + button4 + button5 + button6 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testButtonsWithConfigs() { + if #available(iOS 15.0, *) { + let icon = FBTestImageGenerator.image(with: .systemBlue, size: CGSize(width: 10, height: 10)) + var button0Config = UIButton.Configuration.plain() + button0Config.title = "Button 1" + button0Config.subtitle = "Subtitle 1" + button0Config.image = icon + let button0 = UIButton(configuration: button0Config) + + var button1Config = UIButton.Configuration.plain() + button1Config.title = "Button 1" + button1Config.subtitle = "Subtitle 1" + button1Config.image = icon + button1Config.titlePadding = 8 + button1Config.imagePadding = 4 + let button1 = UIButton(configuration: button1Config) + + var button2Config = UIButton.Configuration.bordered() + button2Config.title = "Button 1" + button2Config.subtitle = "Subtitle 1" + button2Config.image = icon + button2Config.titlePadding = 8 + button2Config.imagePadding = 4 + let button2 = UIButton(configuration: button2Config) + + var button3Config = UIButton.Configuration.tinted() + button3Config.title = "Button 1" + button3Config.subtitle = "Subtitle 1" + button3Config.image = icon + button3Config.titlePadding = 8 + button3Config.imagePadding = 4 + let button3 = UIButton(configuration: button3Config) + + var button4Config = UIButton.Configuration.gray() + button4Config.title = "Button 1" + button4Config.subtitle = "Subtitle 1" + button4Config.image = icon + button4Config.titlePadding = 8 + button4Config.imagePadding = 4 + let button4 = UIButton(configuration: button4Config) + + var button5Config = UIButton.Configuration.filled() + button5Config.title = "Button 1" + button5Config.subtitle = "Subtitle 1" + button5Config.image = icon + button5Config.titlePadding = 8 + button5Config.imagePadding = 4 + let button5 = UIButton(configuration: button5Config) + + let layout = VStack(spacing: 8) { + button0 + button1 + button2 + button3 + button4 + button5 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/UILabelServerSnaposhTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/UILabelServerSnaposhTests.swift new file mode 100644 index 0000000..f560a06 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/UILabelServerSnaposhTests.swift @@ -0,0 +1,299 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class UILabelServerSnaposhTests: FBServerSnapshotTestCase { + + func testLabelsFirstIsLong() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UILabel() + view2.text = "Lorem Ip" + + let layout = HStack { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testLabelsSecondIsLong() { + let view1 = UILabel() + view1.text = "Lorem Ip" + + let view2 = UILabel() + view2.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view2.numberOfLines = 0 + + let layout = HStack { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testLabelsFirstIsLongWithTopAlignment() { + + let view1 = UILabel() + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + + let view2 = UILabel() + view2.text = "Lorem Ip" + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLabelsWithCustomFonts() { + let view1 = UILabel() + view1.font = UIFont.boldSystemFont(ofSize: 20) + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.orange + + let view2 = UILabel() + view2.font = UIFont.boldSystemFont(ofSize: 30) + view2.text = "Lorem Ip" + view2.backgroundColor = ColorPallete.blue + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLabelsWithCustomSetWithAttributedString() { + let view1 = UILabel() + view1.attributedText = NSAttributedString( + string: "Mauris ullamcorper lacus eget enim feugiat rhoncus. Nullam vulputate enim ac lorem consequat faucibus.", + attributes: [.font: UIFont.boldSystemFont(ofSize: 20)]) + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.orange + + let view2 = UILabel() + view2.attributedText = NSAttributedString( + string: "Lorem Ip", + attributes: [.font: UIFont.boldSystemFont(ofSize: 30)]) + view2.backgroundColor = ColorPallete.blue + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLabelsWithCustomFontsWithNewLines() { + let view1 = UILabel() + view1.font = UIFont.boldSystemFont(ofSize: 20) + view1.text = "Mauris ullamcorper lacus eget enim feugiat rhoncus.\n\nNullam vulputate enim ac lorem consequat faucibus." + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.orange + + let view2 = UILabel() + view2.font = UIFont.boldSystemFont(ofSize: 30) + view2.text = "Lorem Ip\n\nEtiam faucibus" + view2.numberOfLines = 0 + view2.backgroundColor = ColorPallete.blue + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLabelsWithCustomSetWithAttributedStringWithNewLines() { + let view1 = UILabel() + view1.attributedText = NSAttributedString( + string: "Mauris ullamcorper lacus eget enim feugiat rhoncus. \n\nNullam vulputate enim ac lorem consequat faucibus.", + attributes: [.font: UIFont.boldSystemFont(ofSize: 20)]) + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.orange + + let view2 = UILabel() + view2.attributedText = NSAttributedString( + string: "Lorem Ip\n\nEtiam faucibus", + attributes: [.font: UIFont.boldSystemFont(ofSize: 30)]) + view2.numberOfLines = 0 + view2.backgroundColor = ColorPallete.blue + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLabelsWithCustomFontsAndEmojies() { + let view1 = UILabel() + view1.font = UIFont.boldSystemFont(ofSize: 20) + view1.text = "Mauris ullamcorper 👪 👨‍👩‍👦 👨‍👩‍👧 👨‍👩‍👧‍👦 👨‍👩‍👦‍👦 👨‍👩‍👧‍👧 👨‍👨‍👦" + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.orange + + let view2 = UILabel() + view2.font = UIFont.boldSystemFont(ofSize: 30) + view2.text = "🥳 🙂‍↕️ 😏 😒" + view2.backgroundColor = ColorPallete.blue + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLabelsWithCustomSetWithAttributedStringAndEmojies() { + let view1 = UILabel() + view1.attributedText = NSAttributedString( + string: "Mauris ullamcorper 👪 👨‍👩‍👦 👨‍👩‍👧\n\n👨‍👩‍👧‍👦 👨‍👩‍👦‍👦 👨‍👩‍👧‍👧 👨‍👨‍👦", + attributes: [.font: UIFont.boldSystemFont(ofSize: 20)]) + view1.numberOfLines = 0 + view1.backgroundColor = ColorPallete.orange + + let view2 = UILabel() + view2.numberOfLines = 0 + view2.attributedText = NSAttributedString( + string: "🥳 🙂‍↕️\n\n😏 😒", + attributes: [.font: UIFont.boldSystemFont(ofSize: 30)]) + view2.backgroundColor = ColorPallete.blue + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLongLabels() { + + let view1 = UILabel() + view1.text = "Vestibulum sit amet magna erat. Nullam sed mi snatch sit amet" + view1.numberOfLines = 0 + + let view2 = UILabel() + view2.text = "Vestibulum sit amet magna erat. Nullam sed mi snatch sit amet" + view2.numberOfLines = 0 + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testTwoLongLabelsWithLayoutPriority() { + let view1 = UILabel() + view1.text = "Vestibulum sit amet magna erat. Nullam sed mi snatch sit amet" + view1.numberOfLines = 0 + + let view2 = UILabel() + view2.text = "Vestibulum sit amet magna erat. Nullam sed mi snatch sit amet" + view2.numberOfLines = 0 + + let layout = HStack(alignment: .top) { + view1 + Spacer(8) + view2 + .layoutPriority(1) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 320, height: 480)) + ) + } + + func testSizeRoundDown() { + let font = UIFont.systemFont(ofSize: 19) + let text = "Hello World!\ntwo lines" + + let height = NSAttributedString(string: text, attributes: [.font: font]) + .boundingRect(with: CGSize(width: 150.0, height: font.lineHeight * 2), options: .usesLineFragmentOrigin, context: nil) + .height + + let label = UILabel() + label.font = font + label.text = text + label.numberOfLines = 2 + label.layer.borderColor = UIColor.systemGray.cgColor + label.layer.borderWidth = 1 + + let layout = HStack { + label + .frame(height: height - 0.111) + } + + takeSnapshot( + with: layout, + in: .exact(CGSize(width: 150, height: 150)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/VFlowServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/VFlowServerSnapshotTests.swift new file mode 100644 index 0000000..0ee8283 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/VFlowServerSnapshotTests.swift @@ -0,0 +1,444 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +final class VFlowServerSnapShotTests: FBServerSnapshotTestCase { + + func buildVFlowSingleChild( + itemAlignment: HorizontalAlignment, + lineAlignment: VerticalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat + ) { + + let view = ColorView(ColorPallete.red, text: "1") + + let VFlow = VFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view + } + takeSnapshot( + with: VFlow, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildVFlowSingleFixedChild( + itemAlignment: HorizontalAlignment, + lineAlignment: VerticalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat + ) { + + let view = ColorView(ColorPallete.red, text: "1") + + let VFlow = VFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view.frame(width: 100, height: 100) + } + + takeSnapshot( + with: VFlow, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func buildVflowMultipleChildrenSameSizeSingleLine( + itemAlignment: HorizontalAlignment, lineAlignment: VerticalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat, + layoutDirection: LayoutDirection = .leftToRight + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let VFlow = VFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + + } + .layoutDirection(layoutDirection) + + takeSnapshot( + with: VFlow, + in: .proposed(CGSize(width: 150, height: 150)), + layoutDirection: layoutDirection + ) + + } + + func buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: HorizontalAlignment, lineAlignment: VerticalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat, + layoutDirection: LayoutDirection = .leftToRight + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + + let VFlow = VFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 60, height: 50) + view2 + .frame(width: 40, height: 70) + view3 + .frame(width: 50, height: 40) + + } + .layoutDirection(layoutDirection) + + takeSnapshot( + with: VFlow, + in: .proposed(CGSize(width: 150, height: 150)), + layoutDirection: layoutDirection + ) + + } + + func buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: HorizontalAlignment, lineAlignment: VerticalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat, + layoutDirection: LayoutDirection = .leftToRight + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(.green, text: "5") + + let VFlow = VFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + view4 + .frame(width: 50, height: 50) + view5 + .frame(width: 50, height: 50) + } + .layoutDirection(layoutDirection) + + takeSnapshot( + with: VFlow, + in: .proposed(CGSize(width: 150, height: 150)), + layoutDirection: layoutDirection + ) + + } + + func buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: HorizontalAlignment, lineAlignment: VerticalAlignment, + itemSpacing: CGFloat, + lineSpacing: CGFloat, + layoutDirection: LayoutDirection = .leftToRight + ) { + + let view1 = ColorView(ColorPallete.red, text: "1") + let view2 = ColorView(ColorPallete.orange, text: "2") + let view3 = ColorView(ColorPallete.blue, text: "3") + let view4 = ColorView(ColorPallete.yellow, text: "4") + let view5 = ColorView(.green, text: "5") + + let VFlow = VFlow(itemAlignment: itemAlignment, lineAlignment: lineAlignment, itemSpacing: itemSpacing, lineSpacing: lineSpacing) { + view1 + .frame(width: 80, height: 50) + view2 + .frame(width: 30, height: 70) + view3 + .frame(width: 50, height: 40) + view4 + .frame(width: 50, height: 50) + view5 + .frame(width: 50, height: 50) + + } + .layoutDirection(layoutDirection) + + takeSnapshot( + with: VFlow, + in: .proposed(CGSize(width: 150, height: 150)), + layoutDirection: layoutDirection + ) + + } + + // MARK: - Single Child + func testSingleChild() { + buildVFlowSingleChild( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testSingleFixedChild() { + buildVFlowSingleFixedChild( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + // MARK: - Multiple Children Same Size Single Line + + func testMultipleChildrenSameSizeSingleLine() { + buildVflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeSingleLineItemSpacing() { + buildVflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeSingleLineLineSpacing() { + buildVflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10) + } + + func testMultipleChildrenSameSizeSingleLineItemAndLineSpacing() { + buildVflowMultipleChildrenSameSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + // Mark - Multiple Children Different Size Single Line + + func testMultipleChildrenDifferentSizeSingleLine() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentLeading() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .leading, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAlignmentTrailing() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .trailing, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeSingleLineAlignmentTop() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .top, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineAlignmentBottom() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .bottom, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeSingleLineItemSpacing() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeSingleLineLineSpacing() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10) + } + + func testMultipleChildrenDifferentSizeSingleLineItemAndLineSpacing() { + buildVflowMultipleChildrenDifferentSizeSingleLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + // Mark - Multiple Children Same Size Multi Line + + func testMultipleChildrenSameSizeMultiLine() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeMultiLineItemAlignmentLeading() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .leading, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeMultiLineItemAlignmentTrailing() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .trailing, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeMultiLineItemSpacing() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0) + } + + func testMultipleChildrenSameSizeMultiLineLineSpacing() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10) + } + + func testMultipleChildrenSameSizeMultiLineItemAndLineSpacing() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + // Mark - Multiple Children Different Size Multi Line + + func testMultipleChildrenDifferentSizeMultiLine() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentLeading() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .leading, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAlignmentTrailing() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .trailing, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeMultiLineAlignmentTop() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .top, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineAlignmentBottom() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .top, + itemSpacing: 0, + lineSpacing: 0 + ) + } + + func testMultipleChildrenDifferentSizeMultiLineItemSpacing() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 0) + } + + func testMultipleChildrenDifferentSizeMultiLineLineSpacing() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 10) + } + + func testMultipleChildrenDifferentSizeMultiLineItemAndLineSpacing() { + buildVflowMultipleChildrenDifferentSizeMultiLine( + itemAlignment: .center, + lineAlignment: .center, + itemSpacing: 10, + lineSpacing: 15 + ) + } + + //Mark - RTL + + func testMultipleChildrenItemAlignmentLeadingRTL() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .leading, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0, + layoutDirection: .rightToLeft + ) + } + + func testMultipleChildrenItemAlignmentTrailingRTL() { + buildVflowMultipleChildrenSameSizeMultiLine( + itemAlignment: .trailing, + lineAlignment: .center, + itemSpacing: 0, + lineSpacing: 0, + layoutDirection: .rightToLeft + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/VStackServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/VStackServerSnapshotTests.swift new file mode 100644 index 0000000..0f32482 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/VStackServerSnapshotTests.swift @@ -0,0 +1,51 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class VStackServerSnapshotTests: FBServerSnapshotTestCase { + + private func runTest(alignment: HorizontalAlignment) { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + + let layout = VStack(alignment: alignment) { + view1 + .frame(width: 100, height: 100) + view2 + .frame(width: 80, height: 80) + view3 + .frame(width: 40, height: 40) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testAlignmentCenter() { + runTest(alignment: .center) + } + + func testAlignmentTop() { + runTest(alignment: .leading) + } + + func testAlignmentBottom() { + runTest(alignment: .trailing) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ViewTransformServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ViewTransformServerSnapshotTests.swift new file mode 100644 index 0000000..54db16f --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ViewTransformServerSnapshotTests.swift @@ -0,0 +1,128 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class ViewTransformServerSnapshotTests: FBServerSnapshotTestCase { + + func testCustomAnchorPoints() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + view1.layer.anchorPoint = .zero + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + view1.layer.anchorPoint = CGPoint(x: 1.0, y: 1.0) + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + view3.layer.anchorPoint = CGPoint(x: 0.5, y: 0.2) + + let layout = HStack { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testCustomAnchorPointsWithTransform() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + view1.layer.anchorPoint = .zero + view1.transform = CGAffineTransformMakeScale(0.9, 0.9) + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + view1.layer.anchorPoint = CGPoint(x: 1.0, y: 1.0) + view2.transform = CGAffineTransformMakeScale(0.9, 0.9) + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + view3.transform = CGAffineTransformMakeScale(0.9, 0.9) + + let layout = HStack { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testScaleTransform() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + view1.transform = CGAffineTransformMakeScale(0.9, 0.9) + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + view2.transform = CGAffineTransformMakeScale(0.9, 0.9) + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + view3.transform = CGAffineTransformMakeScale(0.9, 0.9) + + let layout = HStack { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } + + func testRotateTransform() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + view1.transform = CGAffineTransformMakeRotation(CGFloat.pi / 4) + + let view2 = UIView() + view2.backgroundColor = ColorPallete.yellow + view2.transform = CGAffineTransformMakeRotation(CGFloat.pi / 4) + + let view3 = UIView() + view3.backgroundColor = ColorPallete.red + view3.transform = CGAffineTransformMakeRotation(CGFloat.pi / 4) + + let layout = HStack { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 300, height: 300)) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ZStackServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ZStackServerSnapshotTests.swift new file mode 100644 index 0000000..960bc47 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/ZStackServerSnapshotTests.swift @@ -0,0 +1,189 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class ZStackServerSnaposhTests: FBServerSnapshotTestCase { + + func testSingleUIViewFlexible() { + let view1 = UIView() + view1.backgroundColor = ColorPallete.yellow + + let layout = ZStack { + view1 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testSingleInflexibleUIView() { + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let layout = ZStack { + view0 + .frame(width: 100, height: 100) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testTwoUIViewBothFlexible() { + + let view1 = UIView() + view1.backgroundColor = ColorPallete.yellow + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let layout = ZStack { + view1 + view2 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testTwoUIViewOneFlexible() { + + let view1 = UIView() + view1.backgroundColor = ColorPallete.yellow + let view2 = UIView() + view2.backgroundColor = ColorPallete.blue + + let layout = ZStack { + view1 + view2 + .frame(width: 100, height: 100) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func runTestWithSingleView(alignment: Alignment) { + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let layout = ZStack(alignment: alignment) { + view0 + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testTwoInflexibleUIView() { + + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + let view2 = UIView() + view2.backgroundColor = ColorPallete.orange + + let layout = ZStack { + view0 + view1 + .frame(width: 100, height: 100) + view2 + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func runTestWith(alignment: Alignment) { + let view0 = UIView() + view0.backgroundColor = ColorPallete.yellow + + let view1 = UIView() + view1.backgroundColor = ColorPallete.blue + let view2 = UIView() + view2.backgroundColor = ColorPallete.orange + + let layout = ZStack(alignment: alignment) { + view0 + view1 + .frame(width: 100, height: 100) + view2 + .frame(width: 50, height: 50) + } + + takeSnapshot( + with: layout, + in: .proposed(CGSize(width: 200, height: 200)) + ) + } + + func testCenterAlignment() { + runTestWith(alignment: .center) + } + + func testTopLeadingAlignment() { + runTestWith(alignment: .topLeading) + } + + func testTopAlignment() { + runTestWith(alignment: .top) + } + + func testTopTrailingAlignment() { + runTestWith(alignment: .topTrailing) + } + + func testLeadingAlignment() { + runTestWith(alignment: .leading) + } + + func testTrailingAlignment() { + runTestWith(alignment: .trailing) + } + + func testBottomTrailingAlignment() { + runTestWith(alignment: .bottomTrailing) + } + + func testBottomLeadingAlignment() { + runTestWith(alignment: .bottomLeading) + } + + func testBottomAlignment() { + runTestWith(alignment: .bottom) + } + + func testCenterAlignment_SingleView() { + runTestWithSingleView(alignment: .center) + } + + func testTopLeadingAlignment_SingleView() { + runTestWithSingleView(alignment: .topLeading) + } + + func testTopAlignment_SingleView() { + runTestWithSingleView(alignment: .top) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/_QuickLayoutViewImplementationInvalidSizesServerSnapshotTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/_QuickLayoutViewImplementationInvalidSizesServerSnapshotTests.swift new file mode 100644 index 0000000..2fd28de --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__server_snapshot_tests__/_QuickLayoutViewImplementationInvalidSizesServerSnapshotTests.swift @@ -0,0 +1,68 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import FBServerSnapshotTestCase +import FBTestImageGenerator +import QuickLayoutBridge + +@MainActor +class _QuickLayoutViewImplementationInvalidSizesServerSnapshotTests: FBServerSnapshotTestCase { + + func testProposeInfinity() { + let view = TestView1() + view.label1.text = "Label 1" + view.label2.text = "Label 2" + takeSnapshot( + of: view, + in: .proposed(CGSize(width: 320.0, height: .infinity)) + ) + } + + func testProposeGreatestFiniteMagnitude() { + let view = TestView1() + view.label1.text = "Label 1" + view.label2.text = "Label 2" + takeSnapshot( + of: view, + in: .proposed(CGSize(width: 320.0, height: .greatestFiniteMagnitude)) + ) + } + + func testProposeNan() { + let view = TestView1() + view.label1.text = "Label 1" + view.label2.text = "Label 2" + takeSnapshot( + of: view, + in: .proposed(CGSize(width: 320.0, height: .nan)) + ) + } +} + +private class TestView1: UIView, HasBody { + + let label1 = UILabel() + let label2 = UILabel() + + var body: Layout { + VStack { + label1 + .padding(10) + label2 + } + .frame(maxHeight: .infinity, alignment: .top) + } + + override func layoutSubviews() { + _QuickLayoutViewImplementation.layoutSubviews(self) + super.layoutSubviews() + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? .zero + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/BodyCreationTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/BodyCreationTests.swift new file mode 100644 index 0000000..3a0d313 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/BodyCreationTests.swift @@ -0,0 +1,93 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutBridge +@testable import QuickLayoutBridgeTestUsage + +@MainActor +final class BodyCreationTests: XCTestCase { + + func testMultipleSizeThatFitsWithoutChangingProps() { + let view = BodyCreationTestView() + view.frameSize = CGSize(width: 33, height: 33) + let size = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size.width, 33) + XCTAssertEqual(size.height, 33) + XCTAssertEqual(view.bodyCounter, 1) + + let size2 = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.width, 33) + XCTAssertEqual(size2.height, 33) + XCTAssertEqual(view.bodyCounter, 2) + } + + func testBodyCacheReset() { + let view = BodyCreationTestView() + view.frameSize = CGSize(width: 33, height: 33) + let size = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size.width, 33) + XCTAssertEqual(size.height, 33) + XCTAssertEqual(view.bodyCounter, 1) + + view.setNeedsLayout() + let size2 = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.width, 33) + XCTAssertEqual(size2.height, 33) + XCTAssertEqual(view.bodyCounter, 2) + } + + func testMultipleSizeThatFitsAndChangingProps() { + let view = BodyCreationTestView() + view.frameSize = CGSize(width: 33, height: 33) + let size = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size.width, 33) + XCTAssertEqual(size.height, 33) + XCTAssertEqual(view.bodyCounter, 1) + + view.frameSize = CGSize(width: 34, height: 33) + let size2 = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.width, 34) + XCTAssertEqual(size2.height, 33) + XCTAssertEqual(view.bodyCounter, 2) + } + + func testMultipleLayoutSubviewsWithoutChangingProps() { + let view = BodyCreationTestView() + view.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + view.layoutSubviews() + XCTAssertEqual(view.bodyCounter, 1) + + view.layoutSubviews() + XCTAssertEqual(view.bodyCounter, 2) + } + + func testMultipleLayoutSubviewsWithChangingProps() { + let view = BodyCreationTestView() + view.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + view.layoutSubviews() + XCTAssertEqual(view.bodyCounter, 1) + + view.frameSize = CGSize(width: 34, height: 33) + view.layoutSubviews() + XCTAssertEqual(view.bodyCounter, 2) + } + + func testMultipleFlexibilityForAxisCalls() { + let view = BodyCreationTestView() + view.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + _ = view.quick_flexibility(for: .horizontal) + XCTAssertEqual(view.bodyCounter, 1) + + _ = view.quick_flexibility(for: .horizontal) + XCTAssertEqual(view.bodyCounter, 2) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/BodyCreationWithNestedViewsTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/BodyCreationWithNestedViewsTests.swift new file mode 100644 index 0000000..073201b --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/BodyCreationWithNestedViewsTests.swift @@ -0,0 +1,136 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutBridge +@testable import QuickLayoutBridgeTestUsage + +@MainActor +final class BodyCreationWithNestedViewsTests: XCTestCase { + + func testMultipleSizeThatFitsWithoutChangingProps() { + let view = BodyCreationWithNestedViewsTestView() + view.childView1.leafView.frameSize = CGSize(width: 33, height: 33) + view.childView2.leafView.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + _ = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.childView1.bodyCounter, 2) + XCTAssertEqual(view.childView2.bodyCounter, 2) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 1) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 1) + + _ = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.childView1.bodyCounter, 4) + XCTAssertEqual(view.childView2.bodyCounter, 4) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 2) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 2) + } + + func testMultipleSizeThatFitsAndChangingProps() { + let view = BodyCreationWithNestedViewsTestView() + view.childView1.leafView.frameSize = CGSize(width: 33, height: 33) + view.childView2.leafView.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + _ = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.childView1.bodyCounter, 2) + XCTAssertEqual(view.childView2.bodyCounter, 2) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 1) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 1) + + view.property += 1 + view.childView1.property += 1 + view.childView2.property += 1 + + _ = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.childView1.bodyCounter, 4) + XCTAssertEqual(view.childView2.bodyCounter, 4) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 2) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 2) + } + + func testMultipleLayoutSubviewsWithoutChangingProps() { + let view = BodyCreationWithNestedViewsTestView() + view.childView1.leafView.frameSize = CGSize(width: 33, height: 33) + view.childView2.leafView.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + view.layoutIfNeeded() + view.childView1.layoutIfNeeded() + view.childView2.layoutIfNeeded() + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.childView1.bodyCounter, 3) + XCTAssertEqual(view.childView2.bodyCounter, 3) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 2) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 2) + + view.layoutIfNeeded() + view.childView1.layoutIfNeeded() + view.childView2.layoutIfNeeded() + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.childView1.bodyCounter, 3) + XCTAssertEqual(view.childView2.bodyCounter, 3) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 2) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 2) + } + + func testMultipleLayoutSubviewsWithChangingProps() { + let view = BodyCreationWithNestedViewsTestView() + view.childView1.leafView.frameSize = CGSize(width: 33, height: 33) + view.childView2.leafView.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + view.layoutIfNeeded() + view.childView1.layoutIfNeeded() + view.childView2.layoutIfNeeded() + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.childView1.bodyCounter, 3) + XCTAssertEqual(view.childView2.bodyCounter, 3) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 2) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 2) + + view.property += 1 + view.childView1.property += 1 + view.childView2.property += 1 + + view.layoutIfNeeded() + view.childView1.layoutIfNeeded() + view.childView2.layoutIfNeeded() + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.childView1.bodyCounter, 6) + XCTAssertEqual(view.childView2.bodyCounter, 6) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 4) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 4) + } + + func testMultipleFlexibilityForAxisCalls() { + let view = BodyCreationWithNestedViewsTestView() + view.childView1.leafView.frameSize = CGSize(width: 33, height: 33) + view.childView2.leafView.frameSize = CGSize(width: 33, height: 33) + + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + _ = view.quick_flexibility(for: .horizontal) + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.childView1.bodyCounter, 1) + XCTAssertEqual(view.childView2.bodyCounter, 1) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 0) + + _ = view.quick_flexibility(for: .horizontal) + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.childView1.bodyCounter, 2) + XCTAssertEqual(view.childView2.bodyCounter, 2) + XCTAssertEqual(view.childView1.leafView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.childView2.leafView.sizeThatFitsCounter, 0) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/ComputeGridLayoutSizeThatFitsCountTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ComputeGridLayoutSizeThatFitsCountTests.swift new file mode 100644 index 0000000..3d36a26 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ComputeGridLayoutSizeThatFitsCountTests.swift @@ -0,0 +1,102 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge +import QuickLayoutCore +import UIKit +import XCTest + +@MainActor +class ComputeGridLayoutSizeThatFitsCountTests: XCTestCase { + + func test_SizeThatFitsInvocationCountWithFuzzyLayoutComparison() { + let view1 = TestView() + let view2 = TestView() + + view1.sizeThatFitsBlock = { size in + return CGSize(width: 150.00000000000007, height: 300) + } + + view2.sizeThatFitsBlock = { size in + return CGSize(width: 150.00000000000008, height: 300) + } + + let view = Grid { + + GridRow { + view1 + view2 + } + } + view.sizeThatFits(CGSize(width: 300.0, height: 300.0)) + + XCTAssertEqual(view1.sizeThatFitsCounter, 1) + XCTAssertEqual(view2.sizeThatFitsCounter, 1) + } + + func test_SizeThatFitsInvocationCountWithFixedSizeElements() { + + let view1: TestView = TestView() + let view2: TestView = TestView() + let view3: TestView = TestView() + + let grid = Grid { + GridRow { + view1 + .frame(width: 50, height: 50) + view2 + .frame(width: 50, height: 50) + view3 + .frame(width: 50, height: 50) + } + } + _ = grid.sizeThatFits(CGSize(width: 300, height: 300)) + + XCTAssertEqual(view1.sizeThatFitsCounter, 1) + XCTAssertEqual(view2.sizeThatFitsCounter, 1) + XCTAssertEqual(view3.sizeThatFitsCounter, 1) + + } + + func test_SizeThatFitsInvocationCountMultiplePartialFlexibilityElements() { + let label1 = CountedLabel() + label1.text = "Short text" + let label2 = CountedLabel() + label2.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + let grid = Grid { + GridRow { + label1 + label2 + } + } + _ = grid.sizeThatFits(CGSize(width: 300, height: 300)) + XCTAssertEqual(label1.sizeThatFitsCounter, 1) + XCTAssertEqual(label2.sizeThatFitsCounter, 2) + } + + func test_SizeThatFitsInvocationCountPartialLargerThanProposed() { + + let view1: TestView = TestView() + + let longLabel: CountedLabel = CountedLabel() + longLabel.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + + let grid = Grid { + GridRow { + view1 + .frame(width: 50, height: 50) + longLabel + } + } + _ = grid.sizeThatFits(CGSize(width: 300, height: 300)) + + XCTAssertEqual(view1.sizeThatFitsCounter, 1) + XCTAssertEqual(longLabel.sizeThatFitsCounter, 2) + + } + +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/ComputeStackLayoutSizeThatFitsCountTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ComputeStackLayoutSizeThatFitsCountTests.swift new file mode 100644 index 0000000..c79935d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ComputeStackLayoutSizeThatFitsCountTests.swift @@ -0,0 +1,312 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge +import QuickLayoutCore +import XCTest + +@MainActor +class ComputeStackLayoutSizeThatFitsCountTests: XCTestCase { + + func test_SizeThatFitsInvocationCountWithFuzzyLayoutComparison() { + let view1 = TestView() + let view2 = TestView() + + view1.sizeThatFitsBlock = { size in + return CGSize(width: size.width, height: 150.00000000000007) + } + + view2.sizeThatFitsBlock = { size in + return CGSize(width: size.width, height: 150.00000000000008) + } + + let view = VStack { + view1 + view2 + } + view.sizeThatFits(CGSize(width: 400.0, height: 300.0)) + + XCTAssertEqual(view1.sizeThatFitsCounter, 1) + XCTAssertEqual(view2.sizeThatFitsCounter, 1) + } + + func test_SizeThatFitsInvocationCount_FuzzyComparisonTolerance() { + let view1 = TestView() + let view2 = TestView() + let view3 = TestView() + + view1.sizeThatFitsBlock = { size in + return CGSize(width: size.width, height: 100.0001) + } + + view2.sizeThatFitsBlock = { size in + return CGSize(width: size.width, height: 99.99995) + } + + view3.sizeThatFitsBlock = { size in + /// This view will be double measured because the resulting layout is crossing the fuzzy comparison threshold. + return CGSize(width: size.width, height: 100.0002) + } + + let view = VStack { + view1 + view2 + view3 + } + view.sizeThatFits(CGSize(width: 400.0, height: 300.0)) + + XCTAssertEqual(view1.sizeThatFitsCounter, 1) + XCTAssertEqual(view2.sizeThatFitsCounter, 1) + XCTAssertEqual(view3.sizeThatFitsCounter, 2) + } + + func test_SizeThatFitsInvocationCountWithInfinitySize() { + let view = FeedItemQuicklyView(frame: .zero) + _ = view.sizeThatFits(CGSize(width: 400.0, height: .infinity)) + + // Image Views are wrapped in .resizable() modifier, + // so the Quickly doesn't call sizeThatFits for them. + XCTAssertEqual(view.actorImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.contentImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.posterImageView.sizeThatFitsCounter, 0) + + XCTAssertEqual(view.actionLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.optionsLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterNameLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterHeadlineLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterTimeLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterCommentLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.contentTitleLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.contentDomainLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.likeLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.commentLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.shareLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.actorCommentLabel.sizeThatFitsCounter, 1) + } + + func test_SizeThatFitsInvocationCountWithExactContentSize() { + let viewForMeasure = FeedItemQuicklyView(frame: .zero) + let size = viewForMeasure.sizeThatFits(CGSize(width: 400.0, height: .infinity)) + + let view = FeedItemQuicklyView(frame: .zero) + _ = view.sizeThatFits(size) + + // Image Views are wrapped in .resizable() modifier, + // so the Quickly doesn't call sizeThatFits for them. + XCTAssertEqual(view.actorImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.contentImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.posterImageView.sizeThatFitsCounter, 0) + + XCTAssertEqual(view.actionLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.optionsLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterNameLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterHeadlineLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterTimeLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterCommentLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.contentTitleLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.contentDomainLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.likeLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.commentLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.shareLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.actorCommentLabel.sizeThatFitsCounter, 1) + } + + func test_SizeThatFitsInvocationCountWithSmallerContentSize_40() { + let viewForMeasure = FeedItemQuicklyView(frame: .zero) + var size = viewForMeasure.sizeThatFits(CGSize(width: 400.0, height: .infinity)) + size.height -= 40 + + let view = FeedItemQuicklyView(frame: .zero) + _ = view.sizeThatFits(size) + + // Image Views are wrapped in .resizable() modifier, + // so the Quickly doesn't call sizeThatFits for them. + XCTAssertEqual(view.actorImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.contentImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.posterImageView.sizeThatFitsCounter, 0) + + XCTAssertEqual(view.actionLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.optionsLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterNameLabel.sizeThatFitsCounter, 3) + XCTAssertEqual(view.posterHeadlineLabel.sizeThatFitsCounter, 3) + XCTAssertEqual(view.posterTimeLabel.sizeThatFitsCounter, 3) + XCTAssertEqual(view.posterCommentLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.contentTitleLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.contentDomainLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.likeLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.commentLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.shareLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.actorCommentLabel.sizeThatFitsCounter, 2) + } + + func test_SizeThatFitsInvocationCountWithHardcodedSize400x400() { + + let view = FeedItemQuicklyView(frame: .zero) + _ = view.sizeThatFits(CGSize(width: 400.0, height: 400)) + + // Image Views are wrapped in .resizable() modifier, + // so the Quickly doesn't call sizeThatFits for them. + XCTAssertEqual(view.actorImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.contentImageView.sizeThatFitsCounter, 0) + XCTAssertEqual(view.posterImageView.sizeThatFitsCounter, 0) + + XCTAssertEqual(view.actionLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.optionsLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.posterNameLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterHeadlineLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterTimeLabel.sizeThatFitsCounter, 1) + XCTAssertEqual(view.posterCommentLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.contentTitleLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.contentDomainLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.likeLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.commentLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.shareLabel.sizeThatFitsCounter, 2) + XCTAssertEqual(view.actorCommentLabel.sizeThatFitsCounter, 2) + } +} + +final class FeedItemQuicklyView: UIView { + + func isAutoLayout() -> Bool { + return false + } + + let actionLabel = CountedLabel() + let optionsLabel = CountedLabel() + let posterImageView = FixedSizeViewView(intrinsicSize: CGSize(width: 50, height: 50)) + let posterNameLabel = CountedLabel() + let posterHeadlineLabel = CountedLabel() + let posterTimeLabel = CountedLabel() + let posterCommentLabel = CountedLabel() + let contentImageView = FixedSizeViewView(intrinsicSize: CGSize(width: 350, height: 200)) + let contentTitleLabel = CountedLabel() + let contentDomainLabel = CountedLabel() + let likeLabel = CountedLabel() + let commentLabel = CountedLabel() + let shareLabel = CountedLabel() + let actorImageView = FixedSizeViewView(intrinsicSize: CGSize(width: 50, height: 50)) + let actorCommentLabel = CountedLabel() + + override init(frame: CGRect) { + super.init(frame: frame) + actionLabel.numberOfLines = 0 + posterNameLabel.numberOfLines = 0 + posterHeadlineLabel.numberOfLines = 0 + posterTimeLabel.numberOfLines = 0 + posterCommentLabel.numberOfLines = 0 + contentTitleLabel.numberOfLines = 0 + contentDomainLabel.numberOfLines = 0 + actorCommentLabel.numberOfLines = 0 + prepareViewHierarchy() + setData() + prepareLayout() + } + + func prepareViewHierarchy() { + addSubview(actionLabel) + addSubview(optionsLabel) + addSubview(posterImageView) + addSubview(posterNameLabel) + addSubview(posterHeadlineLabel) + addSubview(posterTimeLabel) + addSubview(posterCommentLabel) + addSubview(contentImageView) + addSubview(contentTitleLabel) + addSubview(contentDomainLabel) + addSubview(likeLabel) + addSubview(commentLabel) + addSubview(shareLabel) + addSubview(actorImageView) + addSubview(actorCommentLabel) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setData() { + actionLabel.text = "lorem" + posterNameLabel.text = "lorem" + posterHeadlineLabel.text = "lorem" + posterTimeLabel.text = "lorem" + posterCommentLabel.text = "lorem" + contentTitleLabel.text = "lorem" + contentDomainLabel.text = "lorem" + actorCommentLabel.text = "lorem" + likeLabel.text = "Like" + commentLabel.text = "Comment" + shareLabel.text = "Share" + optionsLabel.text = "..." + setNeedsLayout() + } + + private var layout: Layout? + + func prepareLayout() { + self.layout = VStack(alignment: .leading) { + HStack { + actionLabel + Spacer() + Spacer(8) + optionsLabel + } + + Spacer(8) + HStack(alignment: .top) { + posterImageView + .resizable() + .frame(width: 50, height: 50) + + Spacer(8) + + VStack(alignment: .leading) { + posterNameLabel + Spacer(8) + posterHeadlineLabel + Spacer(8) + posterTimeLabel + } + } + Spacer(8) + posterCommentLabel + Spacer(8) + contentImageView + .resizable() + .frame(height: 200) + Spacer(8) + contentTitleLabel + Spacer(8) + contentDomainLabel + Spacer(8) + HStack { + likeLabel + Spacer() + commentLabel + Spacer() + shareLabel + } + Spacer(8) + HStack(alignment: .top) { + actorImageView + .resizable() + .frame(width: 50, height: 50) + Spacer(8) + actorCommentLabel + } + } + .padding(8) + } + + override func layoutSubviews() { + super.layoutSubviews() + layout?.applyFrame(bounds) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + layout?.sizeThatFits(size) ?? .zero + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/FuzzyComparisonTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/FuzzyComparisonTests.swift new file mode 100644 index 0000000..a358b9e --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/FuzzyComparisonTests.swift @@ -0,0 +1,82 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutCore + +final class FuzzyComparisonTests: XCTestCase { + + func testFuzzyComparisons() { + + XCTAssertEqual(Fuzzy.compare(58.666666666666664, greaterThan: 58.66666666666666), false) + XCTAssertEqual(Fuzzy.compare(83.66666666666667, greaterThan: 83.66666666666666), false) + XCTAssertEqual(Fuzzy.compare(83.66666666666667, greaterThan: 83.66666666666666), false) + + XCTAssertEqual(Fuzzy.compare(58.666666666666664, equalTo: 58.66666666666666), true) + XCTAssertEqual(Fuzzy.compare(83.66666666666667, equalTo: 83.66666666666666), true) + XCTAssertEqual(Fuzzy.compare(83.66666666666667, equalTo: 83.66666666666666), true) + + // Test isEqual + XCTAssertEqual(Fuzzy.compare(1.0, equalTo: 1.0), true) + XCTAssertEqual(Fuzzy.compare(1.0, equalTo: 1.000001), true) + XCTAssertEqual(Fuzzy.compare(1.0, equalTo: 2.0), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, equalTo: Double.nan), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, equalTo: 1.0), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, equalTo: Double.infinity), false) + XCTAssertEqual(Fuzzy.compare(Double.infinity, equalTo: Double.infinity), true) + XCTAssertEqual(Fuzzy.compare(Double.infinity, equalTo: -Double.infinity), false) + XCTAssertEqual(Fuzzy.compare(-Double.infinity, equalTo: Double.infinity), false) + XCTAssertEqual(Fuzzy.compare(-Double.infinity, equalTo: -Double.infinity), true) + XCTAssertEqual(Fuzzy.compare(0.0, equalTo: -0.0), true) + + // Test isLessThan + XCTAssertEqual(Fuzzy.compare(1.0, lessThan: 2.0), true) + XCTAssertEqual(Fuzzy.compare(2.0, lessThan: 1.0), false) + XCTAssertEqual(Fuzzy.compare(1.0, lessThan: 1.0), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, lessThan: 1.0), false) + XCTAssertEqual(Fuzzy.compare(Double.infinity, lessThan: 1.0), false) + + // Test isGreaterThan + XCTAssertEqual(Fuzzy.compare(2.0, greaterThan: 1.0), true) + XCTAssertEqual(Fuzzy.compare(1.0, greaterThan: 2.0), false) + XCTAssertEqual(Fuzzy.compare(1.0, greaterThan: 1.0), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, greaterThan: 1.0), false) + XCTAssertEqual(Fuzzy.compare(-Double.infinity, greaterThan: 1.0), false) + + XCTAssertEqual(Fuzzy.compare(Double.infinity, greaterThan: Double.infinity), false) + XCTAssertEqual(Fuzzy.compare(Double.infinity, greaterThan: -Double.infinity), true) + XCTAssertEqual(Fuzzy.compare(-Double.infinity, greaterThan: Double.infinity), false) + XCTAssertEqual(Fuzzy.compare(-Double.infinity, greaterThan: -Double.infinity), false) + + XCTAssertEqual(Fuzzy.compare(Double.nan, greaterThan: Double.infinity), false) + XCTAssertEqual(Fuzzy.compare(Double.infinity, greaterThan: Double.nan), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, greaterThan: 1.0), false) + XCTAssertEqual(Fuzzy.compare(10, greaterThan: Double.nan), false) + + // Test isLessThanOrEqual + XCTAssertEqual(Fuzzy.compare(1.0, lessThanOrEqual: 2.0), true) + XCTAssertEqual(Fuzzy.compare(1.0, lessThanOrEqual: 1.0), true) + XCTAssertEqual(Fuzzy.compare(2.0, lessThanOrEqual: 1.0), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, lessThanOrEqual: 1.0), false) + XCTAssertEqual(Fuzzy.compare(Double.infinity, lessThanOrEqual: 1.0), false) + + // Test isGreaterThanOrEqual + XCTAssertEqual(Fuzzy.compare(2.0, greaterThanOrEqual: 1.0), true) + XCTAssertEqual(Fuzzy.compare(1.0, greaterThanOrEqual: 1.0), true) + XCTAssertEqual(Fuzzy.compare(1.0, greaterThanOrEqual: 2.0), false) + XCTAssertEqual(Fuzzy.compare(Double.nan, greaterThanOrEqual: 1.0), false) + XCTAssertEqual(Fuzzy.compare(-Double.infinity, greaterThanOrEqual: 1.0), false) + } + + func testTolerance() { + XCTAssertEqual(Fuzzy.compare(1.0001, equalTo: 1.0, tolerance: 0.0001), true) + XCTAssertEqual(Fuzzy.compare(1.00011, equalTo: 1.0, tolerance: 0.0001), false) + XCTAssertEqual(Fuzzy.compare(1.0, equalTo: 1.0001, tolerance: 0.0001), true) + XCTAssertEqual(Fuzzy.compare(1.0, equalTo: 1.00011, tolerance: 0.0001), false) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/LayoutBuilderTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/LayoutBuilderTests.swift new file mode 100644 index 0000000..8cb5702 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/LayoutBuilderTests.swift @@ -0,0 +1,175 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge +import QuickLayoutCore +import XCTest + +// Helper to invoke the result builder +private func buildLayout(@LayoutBuilder _ builder: () -> Layout) -> Layout { + builder() +} + +// Helpers to avoid `warning: will never be executed` +private func isFalse() -> Bool { false } +private func isTrue() -> Bool { true } + +@MainActor +final class LayoutBuilderTests: XCTestCase { + + func testReturnValue() { + let layout = TestElement() + + let result = buildLayout { + layout + } + + if let result = result as? TestElement { + XCTAssertTrue(result.identifier == layout.identifier) + } else { + XCTFail("Expected TestElement") + } + } + + func testReturnOptionalValue() { + let layout: Layout? = nil + + let result = buildLayout { + layout + } + + XCTAssertTrue(result is EmptyElement) + } + + func testReturnOptionalValueNonNil() { + let expectedLayout = TestElement() + let layout: Layout? = expectedLayout + + let result = buildLayout { + layout + } + + if let result = result as? TestElement { + XCTAssertTrue(result.identifier == expectedLayout.identifier) + } else { + XCTFail("Expected TestElement") + } + } + + func testReturnViewsWrappedIntoLayout() { + let view = UILabel() + + let result = buildLayout { + view + } + + XCTAssertTrue(result is SingleElement) + } + + func testReturnOptionalViewsWrappedIntoLayout() { + let view: UILabel? = nil + + let result = buildLayout { + view + } + + XCTAssertTrue(result is EmptyElement) + } + + func testReturnOptionalViewNonNilWrappedIntoLayout() { + let view: UILabel? = UILabel() + + let result = buildLayout { + view + } + + XCTAssertTrue(result is SingleElement) + } + + func testOptionalReturnsSharedWhenFalse() { + let layout = TestElement() + + let result = buildLayout { + if isFalse() { + layout + } + } + XCTAssertTrue(result is EmptyElement) + } + + func testOptionalReturnsValueWhenTrue() { + let layout = TestElement() + + let result = buildLayout { + if isTrue() { + layout + } + } + if let result = result as? TestElement { + XCTAssertTrue(result.identifier == layout.identifier) + } else { + XCTFail("Expected TestElement") + } + } + + func testEitherReturnsFirstWhenTrue() { + let layout1 = TestElement() + let layout2 = TestElement() + + let result = buildLayout { + if isTrue() { + layout1 + } else { + layout2 + } + } + if let result = result as? TestElement { + XCTAssertTrue(result.identifier == layout1.identifier) + } else { + XCTFail("Expected TestElement") + } + } + + func testEitherReturnsSecondWhenFalse() { + let layout1 = TestElement() + let layout2 = TestElement() + + let result = buildLayout { + if isFalse() { + layout1 + } else { + layout2 + } + } + if let result = result as? TestElement { + XCTAssertTrue(result.identifier == layout2.identifier) + } else { + XCTFail("Expected TestElement") + } + } +} + +private class TestElement: Layout { + + let identifier: String = UUID().uuidString + + func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + LayoutNode.empty + } + + func quick_flexibility(for axis: Axis) -> Flexibility { + .partial + } + + func quick_layoutPriority() -> CGFloat { + 0 + } + + func quick_extractViewsIntoArray(_ views: inout [UIView]) { + // no-op + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/MethodOverrideTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/MethodOverrideTests.swift new file mode 100644 index 0000000..de5258e --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/MethodOverrideTests.swift @@ -0,0 +1,77 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutBridge +@testable import QuickLayoutBridgeTestUsage + +@MainActor +final class MethodOverrideTests: XCTestCase { + func testMultipleLayoutSubviewsWithoutChangingProps() { + let view = MethodOverrideTestView() + view.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.layoutSubviewsCounter, 1) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.layoutSubviewsCounter, 2) + } + + func testMultipleLayoutSubviewsWithChangingProps() { + let view = MethodOverrideTestView() + view.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.layoutSubviewsCounter, 1) + + view.frameSize = CGSize(width: 33, height: 34) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.layoutSubviewsCounter, 2) + } + + func testMultipleSizeThatFitsWithoutChangingProps() { + let view = MethodOverrideTestView() + view.frameSize = CGSize(width: 33, height: 33) + let size = (view as UIView).sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size.width, 100) + XCTAssertEqual(size.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 1) + XCTAssertEqual(view.bodyCounter, 0) + + let size2 = (view as UIView).sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.width, 100) + XCTAssertEqual(size2.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 2) + XCTAssertEqual(view.bodyCounter, 0) + } + + func testMultipleSizeThatFitsAndChangingProps() { + let view = MethodOverrideTestView() + view.frameSize = CGSize(width: 33, height: 33) + let size = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size.width, 100) + XCTAssertEqual(size.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 1) + XCTAssertEqual(view.bodyCounter, 0) + + view.frameSize = CGSize(width: 34, height: 33) + let size2 = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.width, 100) + XCTAssertEqual(size2.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 2) + XCTAssertEqual(view.bodyCounter, 0) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/MethodOverrideWithSuperclassTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/MethodOverrideWithSuperclassTests.swift new file mode 100644 index 0000000..a352df2 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/MethodOverrideWithSuperclassTests.swift @@ -0,0 +1,85 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutBridge +@testable import QuickLayoutBridgeTestUsage + +@MainActor +final class MethodOverrideWithSuperclassTests: XCTestCase { + func testMultipleLayoutSubviewsWithoutChangingProps() { + let view = MethodOverrideWithSuperclassTestView() + view.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.layoutSubviewsCounter, 1) + XCTAssertEqual(view.baseLayoutSubviewsCounter, 1) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.layoutSubviewsCounter, 2) + XCTAssertEqual(view.baseLayoutSubviewsCounter, 2) + } + + func testMultipleLayoutSubviewsWithChangingProps() { + let view = MethodOverrideWithSuperclassTestView() + view.frameSize = CGSize(width: 33, height: 33) + view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 1) + XCTAssertEqual(view.layoutSubviewsCounter, 1) + XCTAssertEqual(view.baseLayoutSubviewsCounter, 1) + + view.frameSize = CGSize(width: 33, height: 34) + + (view as UIView).layoutSubviews() + XCTAssertEqual(view.bodyCounter, 2) + XCTAssertEqual(view.layoutSubviewsCounter, 2) + XCTAssertEqual(view.baseLayoutSubviewsCounter, 2) + } + + func testMultipleSizeThatFitsWithoutChangingProps() { + let view = MethodOverrideWithSuperclassTestView() + view.frameSize = CGSize(width: 33, height: 33) + let size = (view as UIView).sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size.width, 100) + XCTAssertEqual(size.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 1) + XCTAssertEqual(view.baseSizeThatFitsCounter, 1) + XCTAssertEqual(view.bodyCounter, 0) + + let size2 = (view as UIView).sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.width, 100) + XCTAssertEqual(size2.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 2) + XCTAssertEqual(view.baseSizeThatFitsCounter, 2) + XCTAssertEqual(view.bodyCounter, 0) + } + + func testMultipleSizeThatFitsAndChangingProps() { + let view = MethodOverrideWithSuperclassTestView() + view.frameSize = CGSize(width: 33, height: 33) + let size = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size.width, 100) + XCTAssertEqual(size.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 1) + XCTAssertEqual(view.baseSizeThatFitsCounter, 1) + XCTAssertEqual(view.bodyCounter, 0) + + view.frameSize = CGSize(width: 34, height: 33) + let size2 = view.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.width, 100) + XCTAssertEqual(size2.height, 100) + XCTAssertEqual(view.sizeThatFitsCounter, 2) + XCTAssertEqual(view.baseSizeThatFitsCounter, 2) + XCTAssertEqual(view.bodyCounter, 0) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/PixelGridRoundingTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/PixelGridRoundingTests.swift new file mode 100644 index 0000000..32de66d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/PixelGridRoundingTests.swift @@ -0,0 +1,283 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutCore + +private struct TestData { + let screenScale: CGFloat + let input: CGFloat + let output: CGFloat +} + +@MainActor +class PixelGridRoundingTests: XCTestCase { + func test3_0() { + let testTada = [ + TestData(screenScale: 3.0, input: 0.99, output: 1.0), + TestData(screenScale: 3.0, input: 0.0, output: 0.0), + TestData(screenScale: 3.0, input: 0.49999999, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.333333333, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.6666666666, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.99999999999, output: 1.0), + TestData(screenScale: 3.0, input: 0.0333333333333, output: 0.0), + + TestData(screenScale: 3.0, input: 0.00, output: 0.0), + + TestData(screenScale: 3.0, input: 0.11, output: 0.0), + TestData(screenScale: 3.0, input: 0.12, output: 0.0), + TestData(screenScale: 3.0, input: 0.13, output: 0.0), + TestData(screenScale: 3.0, input: 0.14, output: 0.0), + TestData(screenScale: 3.0, input: 0.15, output: 0.0), + TestData(screenScale: 3.0, input: 0.16, output: 0.0), + TestData(screenScale: 3.0, input: 0.17, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.18, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.19, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.20, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.30, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.40, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.41, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.42, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.43, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.44, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.45, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.46, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.47, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.48, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.49, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.50, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.60, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.70, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.80, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.81, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.82, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.83, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.84, output: 1.0), + TestData(screenScale: 3.0, input: 0.85, output: 1.0), + TestData(screenScale: 3.0, input: 0.86, output: 1.0), + TestData(screenScale: 3.0, input: 0.87, output: 1.0), + TestData(screenScale: 3.0, input: 0.88, output: 1.0), + TestData(screenScale: 3.0, input: 0.89, output: 1.0), + TestData(screenScale: 3.0, input: 0.90, output: 1.0), + TestData(screenScale: 3.0, input: 1.00, output: 1.0), + ] + + for testData in testTada { + for base in 0...10 { + let input = CGFloat(base) + testData.input + let output = CGFloat(base) + testData.output + let result = roundPositionToPixelGrid(input, screenScale: testData.screenScale) + XCTAssertEqual(result, output, "input \(input)") + + let result2 = roundPositionToPixelGrid(-input, screenScale: testData.screenScale) + XCTAssertEqual(result2, -output, "input \(-input)") + } + } + } + + func test3_0_SizeRounding() { + let testTada = [ + TestData(screenScale: 3.0, input: 0.99, output: 1.0), + TestData(screenScale: 3.0, input: 0.0, output: 0.0), + TestData(screenScale: 3.0, input: 0.49999999, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.333333333, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.6666666666, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.99999999999, output: 1.0), + TestData(screenScale: 3.0, input: 0.0333333333333, output: 0.3333333333333333), + + TestData(screenScale: 3.0, input: 0.00, output: 0.0), + + TestData(screenScale: 3.0, input: 0.11, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.12, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.13, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.14, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.15, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.16, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.17, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.18, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.19, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.20, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.30, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.31, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.32, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.33, output: 0.3333333333333333), + TestData(screenScale: 3.0, input: 0.34, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.35, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.36, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.37, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.38, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.39, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.40, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.41, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.42, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.43, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.44, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.45, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.46, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.47, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.48, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.49, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.50, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.60, output: 0.6666666666666666), + TestData(screenScale: 3.0, input: 0.70, output: 1.0), + TestData(screenScale: 3.0, input: 0.80, output: 1.0), + TestData(screenScale: 3.0, input: 0.81, output: 1.0), + TestData(screenScale: 3.0, input: 0.82, output: 1.0), + TestData(screenScale: 3.0, input: 0.83, output: 1.0), + TestData(screenScale: 3.0, input: 0.84, output: 1.0), + TestData(screenScale: 3.0, input: 0.85, output: 1.0), + TestData(screenScale: 3.0, input: 0.86, output: 1.0), + TestData(screenScale: 3.0, input: 0.87, output: 1.0), + TestData(screenScale: 3.0, input: 0.88, output: 1.0), + TestData(screenScale: 3.0, input: 0.89, output: 1.0), + TestData(screenScale: 3.0, input: 0.90, output: 1.0), + TestData(screenScale: 3.0, input: 1.00, output: 1.0), + ] + + for testData in testTada { + for base in 0...10 { + let input = CGFloat(base) + testData.input + let output = CGFloat(base) + testData.output + let result = roundSizeToPixelGrid(input, screenScale: testData.screenScale) + XCTAssertEqual(result, output, "input \(input)") + + let result2 = roundSizeToPixelGrid(-input, screenScale: testData.screenScale) + XCTAssertEqual(result2, -output, "input \(-input)") + } + } + } + + func test2_0() { + let testTada = [ + TestData(screenScale: 2.0, input: 0.99, output: 1.0), + TestData(screenScale: 2.0, input: 0.0, output: 0.0), + TestData(screenScale: 2.0, input: 0.49999999, output: 0.5), + TestData(screenScale: 2.0, input: 0.75, output: 1.0), + TestData(screenScale: 2.0, input: 0.74, output: 0.5), + TestData(screenScale: 2.0, input: 0.25, output: 0.5), + TestData(screenScale: 2.0, input: 0.56666666666, output: 0.5), + TestData(screenScale: 2.0, input: 0.57777777777, output: 0.5), + TestData(screenScale: 2.0, input: 0.99219191, output: 1.0), + TestData(screenScale: 2.0, input: 0.99999999, output: 1.0), + + TestData(screenScale: 2.0, input: 0.0, output: 0.0), + TestData(screenScale: 2.0, input: 0.1, output: 0.0), + TestData(screenScale: 2.0, input: 0.2, output: 0.0), + TestData(screenScale: 2.0, input: 0.24999, output: 0.5), + TestData(screenScale: 2.0, input: 0.25, output: 0.5), + TestData(screenScale: 2.0, input: 0.3, output: 0.5), + TestData(screenScale: 2.0, input: 0.4, output: 0.5), + TestData(screenScale: 2.0, input: 0.5, output: 0.5), + TestData(screenScale: 2.0, input: 0.6, output: 0.5), + TestData(screenScale: 2.0, input: 0.7, output: 0.5), + TestData(screenScale: 2.0, input: 0.8, output: 1.0), + TestData(screenScale: 2.0, input: 0.9, output: 1.0), + TestData(screenScale: 2.0, input: 1.0, output: 1.0), + ] + + for testData in testTada { + for base in 0...10 { + let input = CGFloat(base) + testData.input + let output = CGFloat(base) + testData.output + let result = roundPositionToPixelGrid(input, screenScale: testData.screenScale) + XCTAssertEqual(result, output, "input \(input)") + + let result2 = roundPositionToPixelGrid(-input, screenScale: testData.screenScale) + XCTAssertEqual(result2, -output, "input \(-input)") + } + } + } + + func test2_0_SizeRounding() { + let testTada = [ + TestData(screenScale: 2.0, input: 0.99, output: 1.0), + TestData(screenScale: 2.0, input: 0.0, output: 0.0), + TestData(screenScale: 2.0, input: 0.49999999, output: 0.5), + TestData(screenScale: 2.0, input: 0.75, output: 1.0), + TestData(screenScale: 2.0, input: 0.74, output: 1.0), + TestData(screenScale: 2.0, input: 0.25, output: 0.5), + TestData(screenScale: 2.0, input: 0.56666666666, output: 1.0), + TestData(screenScale: 2.0, input: 0.57777777777, output: 1.0), + TestData(screenScale: 2.0, input: 0.99219191, output: 1.0), + TestData(screenScale: 2.0, input: 0.99999999, output: 1.0), + + TestData(screenScale: 2.0, input: 0.0, output: 0.0), + TestData(screenScale: 2.0, input: 0.1, output: 0.5), + TestData(screenScale: 2.0, input: 0.2, output: 0.5), + TestData(screenScale: 2.0, input: 0.24999, output: 0.5), + TestData(screenScale: 2.0, input: 0.25, output: 0.5), + TestData(screenScale: 2.0, input: 0.3, output: 0.5), + TestData(screenScale: 2.0, input: 0.4, output: 0.5), + TestData(screenScale: 2.0, input: 0.5, output: 0.5), + TestData(screenScale: 2.0, input: 0.6, output: 1.0), + TestData(screenScale: 2.0, input: 0.7, output: 1.0), + TestData(screenScale: 2.0, input: 0.8, output: 1.0), + TestData(screenScale: 2.0, input: 0.9, output: 1.0), + TestData(screenScale: 2.0, input: 1.0, output: 1.0), + ] + + for testData in testTada { + for base in 0...10 { + let input = CGFloat(base) + testData.input + let output = CGFloat(base) + testData.output + let result = roundSizeToPixelGrid(input, screenScale: testData.screenScale) + XCTAssertEqual(result, output, "input \(input)") + + let result2 = roundSizeToPixelGrid(-input, screenScale: testData.screenScale) + XCTAssertEqual(result2, -output, "input \(-input)") + } + } + } + + func testSpecialRoundingLogic() { + var result: CGFloat = 0.0 + + result = roundPositionToPixelGrid(1.249999, screenScale: 2.0) + XCTAssertEqual(result, 1.5) + + result = roundPositionToPixelGrid(-1.249999, screenScale: 2.0) + XCTAssertEqual(result, -1.5) + } + + func testNegativeValues() { + var result: CGFloat = 0.0 + result = roundPositionToPixelGrid(-0.4, screenScale: 2.0) + XCTAssertEqual(result, -0.5) + + result = roundPositionToPixelGrid(-1.56666666666, screenScale: 2.0) + XCTAssertEqual(result, -1.5) + + result = roundPositionToPixelGrid(-2.00000, screenScale: 3.0) + XCTAssertEqual(result, -2.0) + } + + func testInvalidValues() { + var result: CGFloat = 0.0 + + result = roundPositionToPixelGrid(CGFloat.nan, screenScale: 2.0) + XCTAssertTrue(result.isNaN) + + result = roundPositionToPixelGrid(CGFloat.greatestFiniteMagnitude - 10, screenScale: 2.0) + XCTAssertEqual(result, CGFloat.greatestFiniteMagnitude - 10) + + result = roundPositionToPixelGrid(CGFloat.greatestFiniteMagnitude, screenScale: 2.0) + XCTAssertEqual(result, CGFloat.greatestFiniteMagnitude) + + result = roundPositionToPixelGrid(CGFloat.infinity, screenScale: 2.0) + XCTAssertEqual(result, .infinity) + + result = roundPositionToPixelGrid(CGFloat.nan, screenScale: 3.0) + XCTAssertTrue(result.isNaN) + + result = roundPositionToPixelGrid(CGFloat.greatestFiniteMagnitude - 10, screenScale: 3.0) + XCTAssertEqual(result, CGFloat.greatestFiniteMagnitude - 10) + + result = roundPositionToPixelGrid(CGFloat.infinity, screenScale: 3.0) + XCTAssertEqual(result, .infinity) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/SizeThatFitsCountTestsUtils.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/SizeThatFitsCountTestsUtils.swift new file mode 100644 index 0000000..6366292 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/SizeThatFitsCountTestsUtils.swift @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge +import QuickLayoutCore +import UIKit +import XCTest + +class FixedSizeViewView: UIView { + + var sizeThatFitsCounter = 0 + private let intrinsicSize: CGSize? + + init(intrinsicSize: CGSize? = nil) { + self.intrinsicSize = intrinsicSize + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // Fully flexible if no intrinsic size is set. + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCounter += 1 + return intrinsicSize ?? size + } + + override var intrinsicContentSize: CGSize { + return intrinsicSize ?? .zero + } +} + +class CountedLabel: UILabel { + var sizeThatFitsCounter = 0 + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCounter += 1 + return super.sizeThatFits(size) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/TestView.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/TestView.swift new file mode 100644 index 0000000..bfbf6b0 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/TestView.swift @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import UIKit + +final class TestView: UIView { + + var sizeThatFitsCounter = 0 + var sizeThatFitsBlock: ((CGSize) -> CGSize)? + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCounter += 1 + return sizeThatFitsBlock?(size) ?? .zero + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/ThreadSafetyTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ThreadSafetyTests.swift new file mode 100644 index 0000000..4620d52 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ThreadSafetyTests.swift @@ -0,0 +1,151 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutBridge +@testable @preconcurrency import QuickLayoutCore + +@MainActor +class ThreadSafetyTests: XCTestCase { + + func testSpacerInASingleStack() { + let layout1 = HStack { + Spacer(10) + } + let size1 = layout1.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size1.height, 0) + XCTAssertEqual(size1.width, 10) + + let layout2 = VStack { + Spacer(10) + } + let size2 = layout2.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.height, 10) + XCTAssertEqual(size2.width, 0) + } + + func testSpacerInANestedStacks() { + let layout1 = VStack { + HStack { + Spacer(10) + } + } + let size1 = layout1.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size1.height, 0) + XCTAssertEqual(size1.width, 10) + + let layout2 = HStack { + VStack { + Spacer(10) + } + } + let size2 = layout2.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size2.height, 10) + XCTAssertEqual(size2.width, 0) + } + + func testNestedStacks() { + let element1 = TestElement() + let element2 = TestElement() + let element3 = TestElement() + let element4 = TestElement() + + let layout1 = + HStack { + element1 + VStack { + element2 + HStack { + element3 + VStack { + element4 + } + } + } + } + + let size1 = layout1.sizeThatFits(CGSize(width: 100, height: 100)) + XCTAssertEqual(size1.height, 0) + XCTAssertEqual(size1.width, 0) + XCTAssertEqual(element1.layoutMainAxis, [.horizontal]) + XCTAssertEqual(element1.flexibilityMainAxis, [.horizontal]) + XCTAssertEqual(element2.layoutMainAxis, [.vertical]) + XCTAssertEqual(element2.flexibilityMainAxis, [.vertical]) + XCTAssertEqual(element3.layoutMainAxis, [.horizontal]) + XCTAssertEqual(element3.flexibilityMainAxis, [.horizontal]) + XCTAssertEqual(element4.layoutMainAxis, [.vertical]) + XCTAssertEqual(element4.flexibilityMainAxis, [.vertical]) + } + + func testNestedStacksOnBackgroundQueue() { + let element1 = TestElement() + let element2 = TestElement() + let element3 = TestElement() + let element4 = TestElement() + let layout1 = + HStack { + element1 + VStack { + element2 + HStack { + element3 + VStack { + element4 + } + } + } + } + + let expectation = XCTestExpectation(description: "Layout on background queue") + let queue = DispatchQueue(label: "com.quick_layout.test_queue") + queue.async { + _ = layout1.sizeThatFits(CGSize(width: 100, height: 100)) + expectation.fulfill() + } + wait(for: [expectation], timeout: 15.0) + + XCTAssertEqual(element1.layoutMainAxis, [.horizontal]) + XCTAssertEqual(element1.flexibilityMainAxis, [.horizontal]) + XCTAssertEqual(element2.layoutMainAxis, [.vertical]) + XCTAssertEqual(element2.flexibilityMainAxis, [.vertical]) + XCTAssertEqual(element3.layoutMainAxis, [.horizontal]) + XCTAssertEqual(element3.flexibilityMainAxis, [.horizontal]) + XCTAssertEqual(element4.layoutMainAxis, [.vertical]) + XCTAssertEqual(element4.flexibilityMainAxis, [.vertical]) + } +} + +private class TestElement: Element { + + var layoutMainAxis: Set = [] + var flexibilityMainAxis: Set = [] + + func quickInternal_isSpacer() -> Bool { + return true + } + + func quick_flexibility(for axis: Axis) -> Flexibility { + let mainAxis = LayoutContext.latestMainAxis + flexibilityMainAxis.insert(mainAxis) + return .fixedSize + } + + func quick_layoutPriority() -> CGFloat { + 0 + } + + func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let mainAxis = LayoutContext.latestMainAxis + layoutMainAxis.insert(mainAxis) + return LayoutNode(view: nil, dimensions: ElementDimensions(CGSize.zero)) + } + + func quick_extractViewsIntoArray(_ views: inout [UIView]) { + // no-op + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/UIKitStandardLibrarySizingBehaviourTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/UIKitStandardLibrarySizingBehaviourTests.swift new file mode 100644 index 0000000..1d352dc --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/UIKitStandardLibrarySizingBehaviourTests.swift @@ -0,0 +1,495 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import XCTest + +@testable import QuickLayoutCore + +@MainActor +class UIKitStandardLibrarySizingBehaviourTests: XCTestCase { + + /// UIButton behaves as a fixed size view, but returns partial flexibility. + private func runTestForButton(_ view: UIButton, name: String) { + + let expectedSize = view.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + + let sizeFor0x0 = view.quick_layoutThatFits(.zero).size + let sizeFor1x1 = view.quick_layoutThatFits(CGSize(width: 1, height: 1)).size + let sizeFor20x10 = view.quick_layoutThatFits(CGSize(width: 20, height: 10)).size + let sizeFor320x480 = view.quick_layoutThatFits(CGSize(width: 320, height: 480)).size + let sizeForCGFloatMax = view.quick_layoutThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).size + + XCTAssertEqual(sizeFor0x0, expectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor1x1, expectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor20x10, expectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor320x480, expectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeForCGFloatMax, expectedSize, "Size doesn't match for \(name)") + + let flexibilityX = view.quick_flexibility(for: .horizontal) + let flexibilityY = view.quick_flexibility(for: .vertical) + XCTAssertEqual(Flexibility.partial, flexibilityX, "Flexibility doesn't match for \(name)") + XCTAssertEqual(Flexibility.partial, flexibilityY, "Flexibility doesn't match for \(name)") + } + + private func runTestForLabel(_ view: UILabel, name: String) { + view.numberOfLines = 1 + + let expectedSize = view.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + + var sizeFor0x0 = view.quick_layoutThatFits(.zero).size + var sizeFor1x1 = view.quick_layoutThatFits(CGSize(width: 1, height: 1)).size + var sizeFor20x10 = view.quick_layoutThatFits(CGSize(width: 20, height: 10)).size + var sizeFor320x480 = view.quick_layoutThatFits(CGSize(width: 320, height: 480)).size + var sizeForCGFloatMax = view.quick_layoutThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).size + + XCTAssertEqual(sizeFor0x0, CGSize.zero, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor1x1, CGSize.zero, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor20x10, CGSize(width: 20, height: 10), "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor320x480, expectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeForCGFloatMax, expectedSize, "Size doesn't match for \(name)") + + var flexibilityX = view.quick_flexibility(for: .horizontal) + var flexibilityY = view.quick_flexibility(for: .vertical) + XCTAssertEqual(Flexibility.partial, flexibilityX, "Flexibility doesn't match for \(name)") + XCTAssertEqual(Flexibility.partial, flexibilityY, "Flexibility doesn't match for \(name)") + + view.numberOfLines = 0 + + sizeFor0x0 = view.quick_layoutThatFits(.zero).size + sizeFor1x1 = view.quick_layoutThatFits(CGSize(width: 1, height: 1)).size + sizeFor20x10 = view.quick_layoutThatFits(CGSize(width: 20, height: 1000)).size + sizeFor320x480 = view.quick_layoutThatFits(CGSize(width: 320, height: 480)).size + sizeForCGFloatMax = view.quick_layoutThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).size + + XCTAssertEqual(sizeFor0x0, CGSize.zero, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor1x1, CGSize.zero, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor20x10, view.sizeThatFits(CGSize(width: 20, height: 1000)), "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor320x480, expectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeForCGFloatMax, expectedSize, "Size doesn't match for \(name)") + + flexibilityX = view.quick_flexibility(for: .horizontal) + flexibilityY = view.quick_flexibility(for: .vertical) + XCTAssertEqual(Flexibility.partial, flexibilityX, "Flexibility doesn't match for \(name)") + XCTAssertEqual(Flexibility.partial, flexibilityY, "Flexibility doesn't match for \(name)") + } + + private func runTestForFullyFlexibleView(_ view: UIView, name: String, flexibility: Flexibility = .fullyFlexible) { + let sizeFor0x0 = view.quick_layoutThatFits(.zero).size + let sizeFor1x1 = view.quick_layoutThatFits(CGSize(width: 1, height: 1)).size + let sizeFor320x480 = view.quick_layoutThatFits(CGSize(width: 320, height: 480)).size + let sizeForCGFloatMax = view.quick_layoutThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).size + + XCTAssertEqual(sizeFor0x0, CGSize.zero, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor1x1, CGSize(width: 1, height: 1), "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor320x480, CGSize(width: 320, height: 480), "Size doesn't match for \(name)") + XCTAssertEqual(sizeForCGFloatMax, CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), "Size doesn't match for \(name)") + + let flexibilityX = view.quick_flexibility(for: .horizontal) + let flexibilityY = view.quick_flexibility(for: .vertical) + XCTAssertEqual(flexibility, flexibilityX, "Flexibility doesn't match for \(name)") + XCTAssertEqual(flexibility, flexibilityY, "Flexibility doesn't match for \(name)") + } + + private func runTestForFixedSizeViews(_ view: UIView, exectedSize: CGSize, name: String) { + let sizeFor0x0 = view.quick_layoutThatFits(.zero).size + let sizeFor1x1 = view.quick_layoutThatFits(CGSize(width: 1, height: 1)).size + let sizeFor320x480 = view.quick_layoutThatFits(CGSize(width: 320, height: 480)).size + let sizeForCGFloatMax = view.quick_layoutThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).size + + XCTAssertEqual(sizeFor0x0, exectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor1x1, exectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor320x480, exectedSize, "Size doesn't match for \(name)") + XCTAssertEqual(sizeForCGFloatMax, exectedSize, "Size doesn't match for \(name)") + + let flexibilityX = view.quick_flexibility(for: .horizontal) + let flexibilityY = view.quick_flexibility(for: .vertical) + XCTAssertEqual(Flexibility.fixedSize, flexibilityX, "Flexibility doesn't match for \(name)") + XCTAssertEqual(Flexibility.fixedSize, flexibilityY, "Flexibility doesn't match for \(name)") + } + + private func runTestForHorizontallyExpandableView(_ view: UIView, height: CGFloat, name: String) { + let sizeFor0x0 = view.quick_layoutThatFits(.zero).size + let sizeFor1x1 = view.quick_layoutThatFits(CGSize(width: 1, height: 1)).size + let sizeFor320x480 = view.quick_layoutThatFits(CGSize(width: 320, height: 480)).size + let sizeForCGFloatMax = view.quick_layoutThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).size + + XCTAssertEqual(sizeFor0x0, CGSize(width: 0, height: height), "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor1x1, CGSize(width: 1, height: height), "Size doesn't match for \(name)") + XCTAssertEqual(sizeFor320x480, CGSize(width: 320, height: height), "Size doesn't match for \(name)") + XCTAssertEqual(sizeForCGFloatMax, CGSize(width: CGFloat.greatestFiniteMagnitude, height: height), "Size doesn't match for \(name)") + + let flexibilityX = view.quick_flexibility(for: .horizontal) + let flexibilityY = view.quick_flexibility(for: .vertical) + XCTAssertEqual(Flexibility.fullyFlexible, flexibilityX, "Flexibility doesn't match for \(name)") + XCTAssertEqual(Flexibility.fixedSize, flexibilityY, "Flexibility doesn't match for \(name)") + } + + func testForFullyFlexibleViews() { + let layout = UICollectionViewLayout() + runTestForFullyFlexibleView(UIView(), name: "UIView") + runTestForFullyFlexibleView(UIScrollView(), name: "UIScrollView") + runTestForFullyFlexibleView(UICollectionView(frame: .zero, collectionViewLayout: layout), name: "UICollectionView") + runTestForFullyFlexibleView(UITableView(), name: "UITableView") + runTestForFullyFlexibleView(UITextView(), name: "UITextView") + } + + func testForFixedSizeViews() { + if let image = UIImage(systemName: "paperplane.fill") { + runTestForFixedSizeViews(UIImageView(image: image), exectedSize: image.size, name: "UIImageView") + } else { + XCTFail("Could not find image with name 'paperplane.fill'") + } + + let largeConfig = UIImage.SymbolConfiguration(pointSize: 140, weight: .bold, scale: .large) + if let image = UIImage(systemName: "doc.circle.fill", withConfiguration: largeConfig) { + runTestForFixedSizeViews(UIImageView(image: image), exectedSize: image.size, name: "UIImageView") + } else { + XCTFail("Could not find image with name 'doc.circle.fill'") + } + runTestForFixedSizeViews(UISwitch(), exectedSize: UISwitch().sizeThatFits(CGSize(width: 100, height: 100)), name: "UISwitch") + runTestForFixedSizeViews(UIStepper(), exectedSize: UIStepper().sizeThatFits(CGSize(width: 100, height: 100)), name: "UIStepper") + runTestForFixedSizeViews(UIPageControl(), exectedSize: UIPageControl().sizeThatFits(CGSize(width: 100, height: 100)), name: "UIPageControl") + runTestForFixedSizeViews(UIActivityIndicatorView(), exectedSize: UIActivityIndicatorView().sizeThatFits(CGSize(width: 100, height: 100)), name: "UIActivityIndicatorView") + } + + func testForHorizontallyExpandableViews() { + let maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: .greatestFiniteMagnitude) + runTestForHorizontallyExpandableView(UISlider(), height: UISlider().sizeThatFits(maxSize).height, name: "UISlider") + runTestForHorizontallyExpandableView(UITextField(), height: UITextField().sizeThatFits(maxSize).height, name: "UITextField") + runTestForHorizontallyExpandableView(UISearchBar(), height: UISearchBar().sizeThatFits(maxSize).height, name: "UISearchBar") + runTestForHorizontallyExpandableView(UIProgressView(), height: UIProgressView().sizeThatFits(maxSize).height, name: "UIProgressView") + runTestForHorizontallyExpandableView(UISearchTextField(), height: UISearchTextField().sizeThatFits(maxSize).height, name: "UISearchTextField") + } + + func testLabel() { + let label1 = UILabel() + label1.text = "Hello World" + runTestForLabel(label1, name: "UILabel") + + let label2 = CustomLabel() + label2.text = "Hello World" + runTestForLabel(label2, name: "Custom Label") + + let label3 = CustomLabelWithOverrides() + label3.text = "Hello World" + runTestForLabel(label3, name: "Custom Label") + XCTAssertEqual(8, label3.sizeThatFitsCallCounter, "sizeThatFitsCallCounter doesn't match for \(name)") + } + + func testButton() { + let button1 = UIButton() + button1.setTitle("Hello World", for: .normal) + runTestForButton(button1, name: "UIButton") + + let button2 = CustomButton() + button2.setTitle("Hello World", for: .normal) + runTestForButton(button2, name: "Custom Button") + + let button3 = CustomButtonWithOverrides() + button3.setTitle("Hello World", for: .normal) + XCTAssertEqual(0, button3.sizeThatFitsCallCounter, "CustomButtonWithOverrides sizeThatFitsCallCounter doesn't match") + runTestForButton(button3, name: "Custom Button") + XCTAssertEqual(6, button3.sizeThatFitsCallCounter, "CustomButtonWithOverrides sizeThatFitsCallCounter doesn't match") + } + + func testForCustomViewsWithoutSizeThatFits() { + let layout = UICollectionViewLayout() + + runTestForFullyFlexibleView(CustomView(), name: "CustomView") + runTestForFullyFlexibleView(CustomScrollView(), name: "CustomScrollView") + runTestForFullyFlexibleView(CustomTableView(), name: "CustomTableView") + runTestForFullyFlexibleView(CustomCollectionView(frame: .zero, collectionViewLayout: layout), name: "CustomCollectionView") + runTestForFullyFlexibleView(CustomTextView(), name: "CustomTextView") + + let largeConfig = UIImage.SymbolConfiguration(pointSize: 140, weight: .bold, scale: .large) + if let image = UIImage(systemName: "doc.circle.fill", withConfiguration: largeConfig) { + runTestForFixedSizeViews(CustomImageView(image: image), exectedSize: image.size, name: "UIImageView") + } else { + XCTFail("Could not find image with name 'doc.circle.fill'") + } + + let maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: .greatestFiniteMagnitude) + runTestForHorizontallyExpandableView(CustomTextField(), height: CustomTextField().sizeThatFits(maxSize).height, name: "CustomTextField") + runTestForHorizontallyExpandableView(CustomSearchBar(), height: CustomSearchBar().sizeThatFits(maxSize).height, name: "CustomSearchBar") + runTestForHorizontallyExpandableView(CustomSlider(), height: CustomSlider().sizeThatFits(maxSize).height, name: "CustomSlider") + runTestForHorizontallyExpandableView(CustomProgressView(), height: CustomProgressView().sizeThatFits(maxSize).height, name: "CustomProgressView") + + runTestForFixedSizeViews(CustomSwitch(), exectedSize: CustomSwitch().sizeThatFits(CGSize(width: 100, height: 100)), name: "CustomSwitch") + runTestForFixedSizeViews(CustomStepper(), exectedSize: CustomStepper().sizeThatFits(CGSize(width: 100, height: 100)), name: "CustomStepper") + runTestForFixedSizeViews(CustomPageControl(), exectedSize: CustomPageControl().sizeThatFits(CGSize(width: 100, height: 100)), name: "CustomPageControl") + runTestForFixedSizeViews(CustomActivityIndicatorView(), exectedSize: CustomActivityIndicatorView().sizeThatFits(CGSize(width: 100, height: 100)), name: "CustomActivityIndicatorView") + } + + func testCustomFullyFlexblyViewsWithMethodOverrides() { + let layout = UICollectionViewLayout() + let view = CustomViewWithOverrides() + let scrollView = CustomScrollViewWithOverrides() + let tableView = CustomTableViewWithOverrides() + let collectionView = CustomCollectionViewWithOverrides(frame: .zero, collectionViewLayout: layout) + let textView = CustomTextViewWithOverrides() + + let viewCounter = view.sizeThatFitsCallCounter + let scrollViewCounter = scrollView.sizeThatFitsCallCounter + let tableViewCounter = tableView.sizeThatFitsCallCounter + let collectionViewCounter = collectionView.sizeThatFitsCallCounter + let textViewCounter = textView.sizeThatFitsCallCounter + + _ = view.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = scrollView.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = tableView.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = collectionView.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = textView.quick_layoutThatFits(CGSize(width: 10, height: 10)) + + XCTAssertEqual(viewCounter + 1, view.sizeThatFitsCallCounter) + XCTAssertEqual(scrollViewCounter + 1, scrollView.sizeThatFitsCallCounter) + XCTAssertEqual(tableViewCounter + 1, tableView.sizeThatFitsCallCounter) + XCTAssertEqual(collectionViewCounter + 1, collectionView.sizeThatFitsCallCounter) + XCTAssertEqual(textViewCounter + 1, textView.sizeThatFitsCallCounter) + + XCTAssertEqual(Flexibility.partial, view.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.partial, view.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, scrollView.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fullyFlexible, scrollView.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, tableView.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fullyFlexible, tableView.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, collectionView.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fullyFlexible, collectionView.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, textView.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fullyFlexible, textView.quick_flexibility(for: .vertical)) + } + + func testCustomFixedViewsWithMethodOverrides() { + let activityView = CustomActivityIndicatorWithOverrides() + let stepper = CustomStepperWithOverrides() + let switchView = CustomSwitchWithOverrides() + let pageControl = CustomPageControlWithOverrides() + + let activityViewCounter = activityView.sizeThatFitsCallCounter + let stepperCounter = stepper.sizeThatFitsCallCounter + let switchViewCounter = switchView.sizeThatFitsCallCounter + let pageControlCounter = pageControl.sizeThatFitsCallCounter + + _ = activityView.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = stepper.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = switchView.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = pageControl.quick_layoutThatFits(CGSize(width: 10, height: 10)) + + XCTAssertEqual(activityViewCounter + 1, activityView.sizeThatFitsCallCounter) + XCTAssertEqual(stepperCounter + 1, stepper.sizeThatFitsCallCounter) + XCTAssertEqual(switchViewCounter + 1, switchView.sizeThatFitsCallCounter) + XCTAssertEqual(pageControlCounter + 1, pageControl.sizeThatFitsCallCounter) + + XCTAssertEqual(Flexibility.fixedSize, activityView.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, activityView.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fixedSize, stepper.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, stepper.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fixedSize, switchView.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, switchView.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fixedSize, pageControl.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, pageControl.quick_flexibility(for: .vertical)) + } + + func testCustomHorizontallyExpandebleViewsWithOverrides() { + let searchBar = CustomSearchBarWithOverrides() + let searchTextField = CustomSearchTextFieldWithOverrides() + let slider = CustomSliderWithOverrides() + let progressView = CustomProgressViewWithOverrides() + let textField = CustomTextFieldWithOverrides() + + let searchBarCounter = searchBar.sizeThatFitsCallCounter + let searchTextFieldCounter = searchTextField.sizeThatFitsCallCounter + let sliderCounter = slider.sizeThatFitsCallCounter + let progressViewCounter = progressView.sizeThatFitsCallCounter + let textFieldCounter = textField.sizeThatFitsCallCounter + + _ = searchBar.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = searchTextField.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = slider.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = progressView.quick_layoutThatFits(CGSize(width: 10, height: 10)) + _ = textField.quick_layoutThatFits(CGSize(width: 10, height: 10)) + + XCTAssertEqual(searchBarCounter + 1, searchBar.sizeThatFitsCallCounter) + XCTAssertEqual(searchTextFieldCounter + 1, searchTextField.sizeThatFitsCallCounter) + XCTAssertEqual(sliderCounter + 1, slider.sizeThatFitsCallCounter) + XCTAssertEqual(progressViewCounter + 1, progressView.sizeThatFitsCallCounter) + XCTAssertEqual(textFieldCounter + 1, textField.sizeThatFitsCallCounter) + + XCTAssertEqual(Flexibility.fullyFlexible, searchBar.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, searchBar.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, searchTextField.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, searchTextField.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, slider.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, slider.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, progressView.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, progressView.quick_flexibility(for: .vertical)) + XCTAssertEqual(Flexibility.fullyFlexible, textField.quick_flexibility(for: .horizontal)) + XCTAssertEqual(Flexibility.fixedSize, textField.quick_flexibility(for: .vertical)) + } +} + +/// Testing custom views, that don't have a sizeThatFits overrides. +private class CustomLabel: UILabel {} +private class CustomButton: UIButton {} +private class CustomView: UIView {} +private class CustomScrollView: UIScrollView {} +private class CustomCollectionView: UICollectionView {} +private class CustomTableView: UITableView {} +private class CustomTextView: UITextView {} +private class CustomTextField: UITextField {} +private class CustomSearchBar: UISearchBar {} +private class CustomSlider: UISlider {} +private class CustomProgressView: UIProgressView {} +private class CustomSearchTextField: UISearchTextField {} +private class CustomImageView: UIImageView {} +private class CustomSwitch: UISwitch {} +private class CustomStepper: UIStepper {} +private class CustomPageControl: UIPageControl {} +private class CustomActivityIndicatorView: UIActivityIndicatorView {} + +private class CustomLabelWithOverrides: UILabel { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomButtonWithOverrides: UIButton { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomViewWithOverrides: UIView { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomScrollViewWithOverrides: UIScrollView { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomTableViewWithOverrides: UITableView { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomTextViewWithOverrides: UITextView { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomCollectionViewWithOverrides: UICollectionView { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomStepperWithOverrides: UIStepper { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomSwitchWithOverrides: UISwitch { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomPageControlWithOverrides: UIPageControl { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomActivityIndicatorWithOverrides: UIActivityIndicatorView { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomTextFieldWithOverrides: UITextField { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomSearchBarWithOverrides: UISearchBar { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomSliderWithOverrides: UISlider { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomProgressViewWithOverrides: UIProgressView { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} + +private class CustomSearchTextFieldWithOverrides: UISearchTextField { + var sizeThatFitsCallCounter = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCallCounter += 1 + return super.sizeThatFits(size) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridge/__tests__/ViewExtractionTests.swift b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ViewExtractionTests.swift new file mode 100644 index 0000000..d6741bd --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridge/__tests__/ViewExtractionTests.swift @@ -0,0 +1,197 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge +import QuickLayoutCore +import XCTest + +@MainActor +class ViewExtractionTests: XCTestCase { + func testViewExtraction() { + + let view1 = UIView() + let view2 = UIView() + let view3 = UIView() + let view4 = UIView() + let view5 = UIView() + let view6 = UIView() + let view7 = UIView() + let view8 = UIView() + + let layout = HStack { + view1 + view2 + VStack { + view3 + view4 + } + ZStack { + view5 + view6 + } + view7 + .overlay { + view8 + } + } + let result = layout.views() + let expectedViews = [view1, view2, view3, view4, view5, view6, view7, view8] + + XCTAssertEqual(result, expectedViews) + } + + func testViewExtractionFromHstackWithConditionalStatements() { + + let view1 = UIView() + let view2 = UIView() + let view3 = UIView() + let view4 = UIView() + let view5 = UIView() + let view6 = UIView() + let view7 = UIView() + + let aBoolValue = true + + let anArray1 = [view4, view5] + let anArray2 = [view6, view7] + + let layout = HStack { + view1 + if !aBoolValue { + view2 + } + if aBoolValue { + view3 + } + for view in anArray1 { + view + } + ForEach(anArray2) + } + let result = layout.views() + let expectedViews = [view1, view3, view4, view5, view6, view7] + + XCTAssertEqual(result, expectedViews) + } + + func testViewExtractionFromZstackWithConditionalStatements() { + + let view1 = UIView() + let view2 = UIView() + let view3 = UIView() + let view4 = UIView() + let view5 = UIView() + let view6 = UIView() + let view7 = UIView() + + let aBoolValue = true + + let anArray1 = [view4, view5] + let anArray2 = [view6, view7] + + let layout = ZStack { + view1 + if !aBoolValue { + view2 + } + if aBoolValue { + view3 + } + for view in anArray1 { + view + } + ForEach(anArray2) + } + let result = layout.views() + let expectedViews = [view1, view3, view4, view5, view6, view7] + + XCTAssertEqual(result, expectedViews) + } + + func testViewExtractionFormLayoutPrimitives() { + + let view1 = UIView() + let view2 = UIView() + let view3 = UIView() + let view4 = UIView() + let view5 = UIView() + let view6 = UIView() + let view7 = UIView() + let view8 = UIView() + + let layout = ZStack { + view1 + EmptyLayout() + view2.frame(width: 100, height: 100) + view3.resizable().frame(width: 100, height: 100) + view4.expand(by: CGSize(width: 1, height: 1)) + Spacer() + view5.frame(minWidth: 100) + view6.aspectRatio(CGSize(width: 1, height: 1)) + view7.layoutPriority(1) + view8.padding(10) + } + let result = layout.views() + let expectedViews = [view1, view2, view3, view4, view5, view6, view7, view8] + + XCTAssertEqual(result, expectedViews) + } + + func testBackgroundAndOverlay() { + + let view1 = UIView() + let view2 = UIView() + let view3 = UIView() + let view4 = UIView() + + let layout = ZStack { + LayeringElement( + target: view1, + layer: view2, + type: .overlay, + alignment: .center + ) + LayeringElement( + target: view3, + layer: view4, + type: .background, + alignment: .center + ) + } + let result = layout.views() + let expectedViews = [view1, view2, view4, view3] + + XCTAssertEqual(result, expectedViews) + } + + func testBackgroundAndOverlayInversed() { + + let view1 = UIView() + let view2 = UIView() + let view3 = UIView() + let view4 = UIView() + + let layout = ZStack { + LayeringElement( + target: view3, + layer: view4, + type: .background, + alignment: .center + ) + LayeringElement( + target: view1, + layer: view2, + type: .background, + alignment: .center + ) + } + let result = layout.views() + let expectedViews = [view4, view3, view2, view1] + + XCTAssertEqual(result, expectedViews) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridgeTestUsage/BodyCreationTestView.swift b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/BodyCreationTestView.swift new file mode 100644 index 0000000..5ffadce --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/BodyCreationTestView.swift @@ -0,0 +1,29 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge + +@QuickLayout +final class BodyCreationTestView: UIView { + + var bodyCounter = 0 + + @Invalidating(.layout) + var frameSize = CGSize.zero + + var body: any Layout { + countedLayout() + } + + func countedLayout() -> any Layout { + bodyCounter += 1 + return VStack { + EmptyLayout() + .frame(width: frameSize.width, height: frameSize.height) + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridgeTestUsage/BodyCreationWithNestedViewsTestView.swift b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/BodyCreationWithNestedViewsTestView.swift new file mode 100644 index 0000000..b639026 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/BodyCreationWithNestedViewsTestView.swift @@ -0,0 +1,70 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge + +final class LeafView: UIView { + var sizeThatFitsCounter = 0 + + @Invalidating(.layout) + var frameSize = CGSize.zero + + @Invalidating(.layout) + var property: Int = 0 + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCounter += 1 + return frameSize + } +} + +@QuickLayout +final class PrivateChildView: UIView { + var bodyCounter: Int = 0 + + @Invalidating(.layout) + var property: Int = 0 + + let leafView = LeafView() + + var body: any Layout { + countedLayout() + } + + private func countedLayout() -> any Layout { + bodyCounter += 1 + return VStack { + leafView + } + } +} + +@QuickLayout +final class BodyCreationWithNestedViewsTestView: UIView { + + var bodyCounter = 0 + + let childView1 = PrivateChildView() + let childView2 = PrivateChildView() + + @Invalidating(.layout) + var property: Int = 0 + + var body: any Layout { + countedLayout() + } + + private func countedLayout() -> any Layout { + bodyCounter += 1 + return HStack { + VStack { + childView1 + childView2 + } + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridgeTestUsage/MethodOverrideTestView.swift b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/MethodOverrideTestView.swift new file mode 100644 index 0000000..a784209 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/MethodOverrideTestView.swift @@ -0,0 +1,41 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge + +@QuickLayout +final class MethodOverrideTestView: UIView { + + var bodyCounter = 0 + var layoutSubviewsCounter = 0 + var sizeThatFitsCounter = 0 + + @Invalidating(.layout) + var frameSize = CGSize.zero + + var body: any Layout { + countedLayout() + } + + private func countedLayout() -> any Layout { + bodyCounter += 1 + return VStack { + EmptyLayout() + .frame(width: frameSize.width, height: frameSize.height) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + layoutSubviewsCounter += 1 + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCounter += 1 + return CGSize(width: 100, height: 100) + } +} diff --git a/Sources/QuickLayout/QuickLayoutBridgeTestUsage/MethodOverrideWithSuperclassTestView.swift b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/MethodOverrideWithSuperclassTestView.swift new file mode 100644 index 0000000..79e6f04 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutBridgeTestUsage/MethodOverrideWithSuperclassTestView.swift @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutBridge + +class BaseView: UIView { + var baseLayoutSubviewsCounter = 0 + var baseSizeThatFitsCounter = 0 + + override func layoutSubviews() { + baseLayoutSubviewsCounter += 1 + super.layoutSubviews() + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + baseSizeThatFitsCounter += 1 + return CGSize(width: 100, height: 100) + } +} + +@QuickLayout +final class MethodOverrideWithSuperclassTestView: BaseView { + + var bodyCounter = 0 + var layoutSubviewsCounter = 0 + var sizeThatFitsCounter = 0 + + @Invalidating(.layout) + var frameSize = CGSize.zero + + var body: any Layout { + countedLayout() + } + + private func countedLayout() -> any Layout { + bodyCounter += 1 + return VStack { + EmptyLayout() + .frame(width: frameSize.width, height: frameSize.height) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + layoutSubviewsCounter += 1 + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + sizeThatFitsCounter += 1 + return super.sizeThatFits(size) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/AlignmentGuides.swift b/Sources/QuickLayout/QuickLayoutCore/AlignmentGuides.swift new file mode 100644 index 0000000..e5a35ff --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/AlignmentGuides.swift @@ -0,0 +1,132 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +// MARK: - AlignmentGuides + +struct AlignmentGuides: Sequence { + struct Iterator: IteratorProtocol { + var impl: Dictionary CGFloat>.Iterator? + mutating func next() -> (key: AnyAlignmentID, value: @Sendable (ElementDimensions) -> CGFloat)? { + impl?.next() + } + } + + private var value: [AnyAlignmentID: @Sendable (ElementDimensions) -> CGFloat]? + + fileprivate init() { + value = nil + } + + fileprivate init(_ value: [AnyAlignmentID: @Sendable (ElementDimensions) -> CGFloat]) { + self.value = value + } + + subscript(_ id: AnyAlignmentID) -> (@Sendable (ElementDimensions) -> CGFloat)? { + get { + value?[id] + } + set { + value = value ?? [:] + value?[id] = newValue + } + } + + func disregarding(_ id: AnyAlignmentID) -> AlignmentGuides { + guard var copy = value else { + return self + } + copy[id] = nil + return AlignmentGuides(copy) + } + + func makeIterator() -> Iterator { + return Iterator(impl: value?.makeIterator()) + } +} + +// MARK: - AlignmentGuidesResolver + +struct AlignmentGuidesResolver { + private static let defaultAlignentIDs: Set = [ + VerticalAlignment.top.alignmentID, + VerticalAlignment.center.alignmentID, + VerticalAlignment.bottom.alignmentID, + HorizontalAlignment.leading.alignmentID, + HorizontalAlignment.center.alignmentID, + HorizontalAlignment.trailing.alignmentID, + ] + + /** + Use this alignment guide result when you are containing a single child, + or multiple children where a single child is the primary child. Multiple + child layouts, such as stacks or grids that contain more than one item + should not use this result. The behavior applied will ensure that alignment + guides are propagated, so that they can be used by a container layout if needed. + */ + static func extract(_ child: LayoutNode.Child) -> AlignmentGuides { + var alignmentGuides = AlignmentGuides() + let position = child.position + let childDimensions = child.layout.dimensions + for (alignment, value) in child.layout.dimensions.alignmentGuides { + alignmentGuides[alignment] = { (dimensions: ElementDimensions) -> CGFloat in + // We need to provide alignment relative to position -- alignment guides + // keep their originating element's reference frame. Child layout dimensions + // are used so that alignment guides recieve the dimensions of the element + // they're being applied to. + switch alignment.axis { + case .horizontal: value(childDimensions) + position.x + case .vertical: value(childDimensions) + position.y + } + } + } + return alignmentGuides + } + + /** + Use this alignment guide result when you are containing multiple children. + In this scenario, custom alignment guides will be propagated, whereas + default alignment guides will not be. Multiple child layouts, such + as stacks, should use this result. + */ + static func extract(for children: [LayoutNode.Child]) -> AlignmentGuides { + var alignmentGuideAggregation = [AnyAlignmentID: [@Sendable (ElementDimensions) -> CGFloat]]() + + /// Using indexed for loop and capturing CGPoint position into the block makes the function ~2x faster. + for i in 0.. CGFloat in + switch alignment.axis { + case .horizontal: value + position.x + case .vertical: value + position.y + } + } + } + } + return AlignmentGuides( + alignmentGuideAggregation.compactMapValues { alignmentArray in + guard alignmentArray.count > 0 else { return nil } + return { dimensions in + // Take the average of all alignment values + return alignmentArray.reduce(0) { $0 + $1(dimensions) } / CGFloat(alignmentArray.count) + } + }) + } + + /** + Use this alignment guide result when you are creating a leaf layout + node that has no children. + */ + static func none() -> AlignmentGuides { + return AlignmentGuides() + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Element+UIKit.swift b/Sources/QuickLayout/QuickLayoutCore/Element+UIKit.swift new file mode 100644 index 0000000..0659b56 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Element+UIKit.swift @@ -0,0 +1,199 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +@MainActor +private let uiViewSizeThatFitsSelector = #selector(UIView.sizeThatFits(_:)) + +@MainActor +private let uiViewSizeThatFitsInstanceMethod = UIView.instanceMethod(for: uiViewSizeThatFitsSelector) + +private func sanitizeSize(_ size: CGSize) -> CGSize { + CGSize( + width: size.width.isFinite ? size.width : 10, + height: size.height.isFinite ? size.height : 10 + ) +} + +// patternlint-disable-next-line retroactive-conformance-systemlib +extension UIView: @preconcurrency LeafElement { + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + assert(Thread.isMainThread, "UIViews can be laid out only on the main thread.") + + let viewType = self.quick_viewType() + switch viewType { + case .baseView: return BaseViewLayout.layoutThatFits(view: self, proposedSize: proposedSize) + case .label: return LabelViewLayout.layoutThatFits(view: self, proposedSize: proposedSize) + case .horizontallyExpandable: return HorizontallyExpandableViewLayout.layoutThatFits(view: self, proposedSize: proposedSize) + case .scrollView: return ScrollViewLayout.layoutThatFits(view: self, proposedSize: proposedSize) + case .tableView: return TableViewLayout.layoutThatFits(view: self, proposedSize: proposedSize) + case .textView: return TextViewLayout.layoutThatFits(view: self, proposedSize: proposedSize) + case .fixedSize: return FixedSizeViewLayout.layoutThatFits(view: self, proposedSize: proposedSize) + } + } + + @objc + open func quick_flexibility(for axis: Axis) -> Flexibility { + assert(Thread.isMainThread, "UIViews can be laid out only on the main thread.") + + let viewType = self.quick_viewType() + switch viewType { + case .baseView: return BaseViewLayout.flexibility(for: axis, view: self) + case .label: return LabelViewLayout.flexibility(for: axis) + case .horizontallyExpandable: return HorizontallyExpandableViewLayout.flexibility(for: axis) + case .scrollView: return ScrollViewLayout.flexibility(for: axis) + case .textView: return TextViewLayout.flexibility(for: axis) + case .tableView: return TableViewLayout.flexibility(for: axis) + case .fixedSize: return FixedSizeViewLayout.flexibility(for: axis) + } + } + + public func quick_layoutPriority() -> CGFloat { + return 0 + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + views.append(self) + } + + public func backingView() -> UIView? { + self + } +} + +@MainActor +private struct BaseViewLayout { + + @MainActor + private static func measure(_ view: UIView, _ proposedSize: CGSize) -> CGSize { + if view.method(for: uiViewSizeThatFitsSelector) != uiViewSizeThatFitsInstanceMethod { + return view.sizeThatFits(proposedSize) + } + return proposedSize + } + + static func layoutThatFits(view: UIView, proposedSize: CGSize) -> LayoutNode { + let size = measure(view, proposedSize) + return LayoutNode(view: view, dimensions: ElementDimensions(sanitizeSize(size))) + } + + static func flexibility(for axis: Axis, view: UIView) -> Flexibility { + if view.method(for: uiViewSizeThatFitsSelector) != uiViewSizeThatFitsInstanceMethod { + return .partial + } + return .fullyFlexible + } +} + +@MainActor +private struct LabelViewLayout { + + static func layoutThatFits(view: UIView, proposedSize: CGSize) -> LayoutNode { + var size = CGSize.zero + if proposedSize.width > 1 && proposedSize.height > 1 { + /* + Below, the measured size is clamped with the constraining size + to ensure the label doesn't grow beyond the proposed size. + This is OK but not ideal because clamping wouldn't round to the nearest visible line + leading to extra space on top and bottom. + Learn more: https://facebookincubator.github.io/QuickLayout/layout/calculate-size-label/#label-layout-implementation-in-quicklayout + */ + size = view.sizeThatFits(proposedSize) + size.width = roundSizeToPixelGrid(min(size.width, proposedSize.width)) + size.height = roundSizeToPixelGrid(min(size.height, proposedSize.height)) + } + return LayoutNode(view: view, dimensions: ElementDimensions(size)) + } + + static func flexibility(for axis: Axis) -> Flexibility { + return .partial + } +} + +@MainActor +private struct HorizontallyExpandableViewLayout { + + static func layoutThatFits(view: UIView, proposedSize: CGSize) -> LayoutNode { + let height = view.sizeThatFits(proposedSize).height + let size = CGSize(width: proposedSize.width, height: height) + return LayoutNode(view: view, dimensions: ElementDimensions(sanitizeSize(size))) + } + + static func flexibility(for axis: Axis) -> Flexibility { + switch axis { + case .horizontal: return .fullyFlexible + case .vertical: return .fixedSize + } + } +} + +@MainActor +private struct TableViewLayout { + + static func layoutThatFits(view: UIView, proposedSize: CGSize) -> LayoutNode { + var size = proposedSize + let selector = #selector(UITableView.sizeThatFits(_:)) + if view.method(for: selector) != UITableView.instanceMethod(for: selector) { + size = view.sizeThatFits(proposedSize) + } + return LayoutNode(view: view, dimensions: ElementDimensions(sanitizeSize(size))) + } + + static func flexibility(for axis: Axis) -> Flexibility { + .fullyFlexible + } +} + +@MainActor +private struct ScrollViewLayout { + + static func layoutThatFits(view: UIView, proposedSize: CGSize) -> LayoutNode { + var size = proposedSize + let selector = #selector(UIScrollView.sizeThatFits(_:)) + if view.method(for: selector) != UIScrollView.instanceMethod(for: selector) { + size = view.sizeThatFits(proposedSize) + } + return LayoutNode(view: view, dimensions: ElementDimensions(sanitizeSize(size))) + } + + static func flexibility(for axis: Axis) -> Flexibility { + .fullyFlexible + } +} + +@MainActor +private struct TextViewLayout { + + static func layoutThatFits(view: UIView, proposedSize: CGSize) -> LayoutNode { + var size = proposedSize + let selector = #selector(UITextView.sizeThatFits(_:)) + if view.method(for: selector) != UITextView.instanceMethod(for: selector) { + size = view.sizeThatFits(proposedSize) + } + return LayoutNode(view: view, dimensions: ElementDimensions(sanitizeSize(size))) + } + + static func flexibility(for axis: Axis) -> Flexibility { + .fullyFlexible + } +} + +@MainActor +private struct FixedSizeViewLayout { + + static func layoutThatFits(view: UIView, proposedSize: CGSize) -> LayoutNode { + let size = view.sizeThatFits(proposedSize) + return LayoutNode(view: view, dimensions: ElementDimensions(sanitizeSize(size))) + } + + static func flexibility(for axis: Axis) -> Flexibility { + .fixedSize + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Element+ViewTypes.swift b/Sources/QuickLayout/QuickLayoutCore/Element+ViewTypes.swift new file mode 100644 index 0000000..3815dc3 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Element+ViewTypes.swift @@ -0,0 +1,132 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +/// If you add a new view type or override a new view please add a test into +/// UIKitStandardLibrarySizingBehaviourTests.swift, +/// HStackWithTwoViewsServerSnapshotTests.swift, +/// HStackWithTwoViewsAndInfinitSizeServerSnapshotTests.swift. + +@objc +/// While Flexibility specifies sizing behavior of a view per axis, +/// ViewType specifies sizing behaviour of a UIKit's view in both axis. +enum ViewType: Int8 { + case baseView + case label + case horizontallyExpandable + case textView + case scrollView + case tableView + case fixedSize +} + +extension UIView { + @objc + func quick_viewType() -> ViewType { + return .baseView + } +} + +extension UILabel { + @objc + override func quick_viewType() -> ViewType { + return .label + } +} + +extension UITextField { + @objc + override func quick_viewType() -> ViewType { + return .horizontallyExpandable + } +} + +extension UISearchBar { + @objc + override func quick_viewType() -> ViewType { + return .horizontallyExpandable + } +} + +extension UISlider { + @objc + override func quick_viewType() -> ViewType { + return .horizontallyExpandable + } +} + +extension UIProgressView { + @objc + override func quick_viewType() -> ViewType { + return .horizontallyExpandable + } +} + +extension UISearchTextField { + @objc + override func quick_viewType() -> ViewType { + return .horizontallyExpandable + } +} + +extension UITextView { + @objc + override func quick_viewType() -> ViewType { + return .textView + } +} + +extension UITableView { + @objc + override func quick_viewType() -> ViewType { + return .tableView + } +} + +extension UIScrollView { + @objc + override func quick_viewType() -> ViewType { + return .scrollView + } +} + +extension UIImageView { + @objc + override func quick_viewType() -> ViewType { + return .fixedSize + } +} + +extension UIActivityIndicatorView { + @objc + override func quick_viewType() -> ViewType { + return .fixedSize + } +} + +extension UIStepper { + @objc + override func quick_viewType() -> ViewType { + return .fixedSize + } +} + +extension UIPageControl { + @objc + override func quick_viewType() -> ViewType { + return .fixedSize + } +} + +extension UISwitch { + @objc + override func quick_viewType() -> ViewType { + return .fixedSize + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/ElementProtocol.swift b/Sources/QuickLayout/QuickLayoutCore/ElementProtocol.swift new file mode 100644 index 0000000..e493380 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/ElementProtocol.swift @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +/// Every element that can be laid out must conform to this protocol. +public protocol Element { + func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode + func quick_flexibility(for axis: Axis) -> Flexibility + func quick_layoutPriority() -> CGFloat + func quick_extractViewsIntoArray(_ views: inout [UIView]) + func quickInternal_isSpacer() -> Bool +} + +public extension Element { + func quickInternal_isSpacer() -> Bool { + false + } +} + +/// Marker protocol for layout elements that should manage only leaf elements +/// such as UIViews or ViewProxies. +public protocol LeafElement: Element { + func backingView() -> UIView? +} diff --git a/Sources/QuickLayout/QuickLayoutCore/FuzzyComparison.swift b/Sources/QuickLayout/QuickLayoutCore/FuzzyComparison.swift new file mode 100644 index 0000000..e1035db --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/FuzzyComparison.swift @@ -0,0 +1,49 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +/// Fuzzy comparisons for CGFloats, allowing for a tolerance based equality check and inequality check. +/// Note that it uses the default tolerance of 0.0001, as it's sufficient for pixel value comparison. +/// However, if used for other purposes, it's recommended to pass smaller tolerance or use CGFloat.ulp. +/// The following values will be treated as equal with the default tolerance: +/// a = 1.00001 +/// b = 1.000011 +/// or +/// a 58.666666666666664 +/// b 58.66666666666666 +/// +struct Fuzzy { + + @inlinable + static func compare(_ this: T, equalTo other: T, tolerance: T = 0.0001) -> Bool { + if this.isNaN || other.isNaN { return false } + if this == other { return true } + if this.isInfinite || other.isInfinite { return this == other } + return abs(this - other) <= tolerance + } + + @inlinable + static func compare(_ this: T, lessThan other: T, tolerance: T = 0.0001) -> Bool { + this < other && !Fuzzy.compare(this, equalTo: other, tolerance: tolerance) + } + + @inlinable + static func compare(_ this: T, greaterThan other: T, tolerance: T = 0.0001) -> Bool { + this > other && !Fuzzy.compare(this, equalTo: other, tolerance: tolerance) + } + + @inlinable + static func compare(_ this: T, lessThanOrEqual other: T, tolerance: T = 0.0001) -> Bool { + this < other || Fuzzy.compare(this, equalTo: other, tolerance: tolerance) + } + + @inlinable + static func compare(_ this: T, greaterThanOrEqual other: T, tolerance: T = 0.0001) -> Bool { + this > other || Fuzzy.compare(this, equalTo: other, tolerance: tolerance) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeFlowLayout.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeFlowLayout.swift new file mode 100644 index 0000000..c90a469 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeFlowLayout.swift @@ -0,0 +1,110 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +private func layoutLine(children: [LayoutNode], itemSpacing: CGFloat, mainAxis: Axis, itemAlignment: AnyAlignmentID, reverseItems: Bool) -> LayoutNode { + let childrenCount = children.count + var maxAlignmentGuideLength: CGFloat = 0 + for layout in children { + maxAlignmentGuideLength = max(maxAlignmentGuideLength, itemAlignment.defaultValue(in: layout.dimensions)) + } + // Calculate positions with alignment + var offset: CGFloat = 0 + let range = reverseItems ? stride(from: childrenCount - 1, through: 0, by: -1) : stride(from: 0, through: childrenCount - 1, by: 1) + var positionedChildren = range.map { index in + let layout = children[index] + let alignmentGuideLength = itemAlignment.defaultValue(in: layout.dimensions) + let crossAxisOffset = maxAlignmentGuideLength - alignmentGuideLength + let position = CGPoint(main: offset, cross: crossAxisOffset, mainAxis: mainAxis) + offset += layout.size.main(for: mainAxis) + itemSpacing + return LayoutNode.Child(position: roundToPixelGrid(position), layout: layout) + } + // Makes final frame by taking union of calculated positions + var resultFrame: CGRect = .zero + positionedChildren.forEach { child in + let frame = CGRect(origin: child.position, size: child.layout.size) + resultFrame = resultFrame.union(frame) + } + if reverseItems { + positionedChildren.reverse() + } + return LayoutNode(view: nil, size: resultFrame.size, children: positionedChildren, alignmentGuides: AlignmentGuidesResolver.extract(for: positionedChildren)) +} +private func splitNodesIntoLines(main: CGFloat, children: [LayoutNode], itemSpacing: CGFloat, mainAxis: Axis) -> [[LayoutNode]] { + var lines: [[LayoutNode]] = [] + var currentLine: [LayoutNode] = [] + let maxMainSize = main + var totalMainSize: CGFloat = 0 + for child in children { + let childMainSize = child.size.main(for: mainAxis) + let remainingMain = maxMainSize - totalMainSize + if remainingMain >= childMainSize { + // Add child to current line + currentLine.append(child) + totalMainSize += childMainSize + itemSpacing + } else { + // Start a new line + lines.append(currentLine) + currentLine = [child] + totalMainSize = childMainSize + itemSpacing + } + } + // Appends final line + lines.append(currentLine) + return lines +} +func computeFlowLayout(proposedSize: CGSize, children: [Element], itemSpacing: CGFloat, lineSpacing: CGFloat, mainAxis: Axis, itemAlignment: AnyAlignmentID, lineAlignment: AnyAlignmentID) -> LayoutNode { + let isRTL = LayoutContext.layoutDirection == .rightToLeft + let reverseItems = isRTL && mainAxis == .horizontal + let reverseLines = isRTL && mainAxis == .vertical + if children.isEmpty { + return LayoutNode.empty + } + if children.count == 1 { + let childLayout = children[0].quick_layoutThatFits(proposedSize) + let childNode = LayoutNode.Child(position: .zero, layout: childLayout) + return LayoutNode(view: nil, size: childLayout.size, children: [childNode], alignmentGuides: AlignmentGuidesResolver.extract(childNode)) + } + let childrenLayout = children.map { child in + return child.quick_layoutThatFits(proposedSize) + } + let main = proposedSize.main(for: mainAxis) + let lines = splitNodesIntoLines(main: main, children: childrenLayout, itemSpacing: itemSpacing, mainAxis: mainAxis) + let lineNodes = lines.map { line in + layoutLine(children: line, itemSpacing: itemSpacing, mainAxis: mainAxis, itemAlignment: itemAlignment, reverseItems: reverseItems) + } + // Calculate max alignment for lines + var maxAlignmentGuideLength: CGFloat = 0 + for layout in lineNodes { + maxAlignmentGuideLength = max(maxAlignmentGuideLength, lineAlignment.defaultValue(in: layout.dimensions)) + } + // Calculate positions with alignment + var lineOffset: CGFloat = 0 + let linesCount = lineNodes.count + let range = reverseLines ? stride(from: linesCount - 1, through: 0, by: -1) : stride(from: 0, through: linesCount - 1, by: 1) + var positionedLines = range.map { index in + let layout = lineNodes[index] + let alignmentGuideLength = lineAlignment.defaultValue(in: layout.dimensions) + let mainAxisOffset = maxAlignmentGuideLength - alignmentGuideLength + let position = CGPoint(main: mainAxisOffset, cross: lineOffset, mainAxis: mainAxis) + lineOffset += layout.size.cross(for: mainAxis) + lineSpacing + return LayoutNode.Child(position: roundToPixelGrid(position), layout: layout) + } + if reverseLines { + positionedLines.reverse() + } + // Finding container size + var resultFrame: CGRect = .zero + positionedLines.forEach { child in + let frame = CGRect(origin: child.position, size: child.layout.size) + resultFrame = resultFrame.union(frame) + } + // Returns the layout + let finalLayout = LayoutNode(view: nil, size: resultFrame.size, children: positionedLines, alignmentGuides: AlignmentGuidesResolver.extract(for: positionedLines)) + return finalLayout +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeGridLayout.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeGridLayout.swift new file mode 100644 index 0000000..bf0d100 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeGridLayout.swift @@ -0,0 +1,373 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +private struct GridItem { + let horizontalFlexibility: Flexibility + let verticalFlexibility: Flexibility + let layoutPriority: CGFloat +} + +func computeGridLayout(rows: [GridRowElement], alignment: Alignment, proposedSize: CGSize, verticalSpacing: CGFloat, horizontalSpacing: CGFloat) -> LayoutNode { + let rowCount = rows.count + let columnCount = rows.reduce(0) { max($0, $1.children.count) } + + if rowCount == 0 || (rowCount == 1 && columnCount == 0) { + return LayoutNode(view: nil, size: .zero, children: [], alignmentGuides: AlignmentGuidesResolver.none()) + } + + return LayoutContext.$latestMainAxis.withValue(.horizontal) { + if rowCount == 1 && columnCount == 1 { + let childLayout = rows[0].children[0].quick_layoutThatFits(proposedSize) + let childNode = LayoutNode.Child(position: .zero, layout: childLayout) + return LayoutNode(view: nil, size: childLayout.size, children: [childNode], alignmentGuides: AlignmentGuidesResolver.extract(childNode)) + } + + let availableSize = CGSize( + width: max(0, proposedSize.width - (CGFloat(columnCount - 1) * horizontalSpacing)), + height: max(0, proposedSize.height - (CGFloat(rowCount - 1) * verticalSpacing)) + ) + + // Step 1: Build metadata for each cell. + let gridItems = buildGridItems( + rows: rows, + rowCount: rowCount, + columnCount: columnCount + ) + + // Step 2: Calculate the layout of each cell. + let cellLayouts = layoutCellsContent( + in: availableSize, + rows: rows, + gridItems: gridItems, + rowCount: rowCount, + columnCount: columnCount + ) + + let columnAlignments = extractColumnAlignments(from: cellLayouts, columnCount: columnCount) + let rowAlignments = extractRowAlignments(from: rows, rowCount: rowCount) + + // Step 3: Position content within their own cell. + let positionedChildren = positionContentInCells( + layouts: cellLayouts, + alignment: alignment, + columnAlignments: columnAlignments, + rowAlignments: rowAlignments, + verticalSpacing: verticalSpacing, + horizontalSpacing: horizontalSpacing, + rowCount: rowCount, + columnCount: columnCount + ) + return positionedChildren + } +} + +@inline(__always) +private func buildGridItems(rows: [GridRowElement], rowCount: Int, columnCount: Int) -> [[GridItem]] { + let emptyItem = GridItem( + horizontalFlexibility: .fixedSize, + verticalFlexibility: .fixedSize, + layoutPriority: -CGFloat.infinity + ) + var gridItems: [[GridItem]] = [] + gridItems.reserveCapacity(rowCount) + for row in rows { + var rowItems: [GridItem] = [] + rowItems.reserveCapacity(columnCount) + for child in row.children { + let item = GridItem( + horizontalFlexibility: child.quick_flexibility(for: .horizontal), + verticalFlexibility: child.quick_flexibility(for: .vertical), + layoutPriority: child.quick_layoutPriority() + ) + rowItems.append(item) + } + while rowItems.count < columnCount { + rowItems.append(emptyItem) + } + gridItems.append(rowItems) + } + return gridItems +} + +@inline(__always) +private func layoutCellsContent( + in availableSize: CGSize, + rows: [GridRowElement], + gridItems: [[GridItem]], + rowCount: Int, + columnCount: Int +) -> [[LayoutNode?]] { + var cellLayouts: [[LayoutNode?]] = Array(repeating: Array(repeating: nil, count: columnCount), count: rowCount) + var lastProposedSizes: [[CGSize?]] = Array(repeating: Array(repeating: nil, count: columnCount), count: rowCount) + var minColumnWidths: [CGFloat] = Array(repeating: 0, count: columnCount) + var minRowHeights: [CGFloat] = Array(repeating: 0, count: rowCount) + var maxColumnWidths: [CGFloat] = Array(repeating: 0, count: columnCount) + var maxRowHeights: [CGFloat] = Array(repeating: 0, count: rowCount) + var maxColumnLayoutPriority: [CGFloat] = Array(repeating: -.infinity, count: columnCount) + var maxRowLayoutPriority: [CGFloat] = Array(repeating: -.infinity, count: rowCount) + var maxColumnFlexibility: [Flexibility] = Array(repeating: .fixedSize, count: columnCount) + var maxRowFlexibility: [Flexibility] = Array(repeating: .fixedSize, count: rowCount) + + for (rowIndex, row) in rows.enumerated() { + for (columnIndex, child) in row.children.enumerated() { + let item = gridItems[rowIndex][columnIndex] + maxColumnLayoutPriority[columnIndex] = max(maxColumnLayoutPriority[columnIndex], item.layoutPriority) + maxRowLayoutPriority[rowIndex] = max(maxRowLayoutPriority[rowIndex], item.layoutPriority) + maxColumnFlexibility[columnIndex] = max(maxColumnFlexibility[columnIndex], item.horizontalFlexibility) + maxRowFlexibility[rowIndex] = max(maxRowFlexibility[rowIndex], item.verticalFlexibility) + + if item.horizontalFlexibility == .fullyFlexible && item.verticalFlexibility == .fullyFlexible { + maxColumnWidths[columnIndex] = .infinity + maxRowHeights[rowIndex] = .infinity + continue + } + let layout = child.quick_layoutThatFits(availableSize) + cellLayouts[rowIndex][columnIndex] = layout + lastProposedSizes[rowIndex][columnIndex] = availableSize + + let width = item.horizontalFlexibility == .fullyFlexible ? .infinity : layout.size.width + let height = item.verticalFlexibility == .fullyFlexible ? .infinity : layout.size.height + maxColumnWidths[columnIndex] = max(maxColumnWidths[columnIndex], width) + maxRowHeights[rowIndex] = max(maxRowHeights[rowIndex], height) + + if item.horizontalFlexibility == .fixedSize { + minColumnWidths[columnIndex] = max(minColumnWidths[columnIndex], width) + } + if item.verticalFlexibility == .fixedSize { + minRowHeights[rowIndex] = max(minRowHeights[rowIndex], height) + } + } + } + + var availableSize = availableSize + + for columnIndex in 0..) { + if let columnIndices = groupedColumnIndices[key] { + let columnIndices = columnIndices.sorted { + maxColumnWidths[$0] < maxColumnWidths[$1] + } + var count = columnIndices.count + 1 + for columnIndex in columnIndices { + count -= 1 + if maxColumnFlexibility[columnIndex] == .fixedSize { continue } + + var columnWidth = 0.0 + for rowIndex in 0..) { + if let rowIndices = groupedRowIndices[key] { + let rowIndices = rowIndices.sorted { + maxRowHeights[$0] < maxRowHeights[$1] + } + var count = rowIndices.count + 1 + var rowHeight = 0.0 + for rowIndex in rowIndices { + count -= 1 + + for columnIndex in 0.. [CGFloat: [Int]] { + var result = [CGFloat: [Int]]() + for (index, priority) in maxLayoutPriority.enumerated() { + result[priority, default: []].append(index) + } + return result +} + +private func find(in layoutNode: LayoutNode, valueBlock: (GridInfo) -> T?) -> T? { + if let gridInfo = layoutNode.gridInfo, let value = valueBlock(gridInfo) { + return value + } + if let child = layoutNode.children.first, layoutNode.children.count == 1 { + return find(in: child.layout, valueBlock: valueBlock) + } + return nil +} + +@inline(__always) +private func positionContentInCells( + layouts: [[LayoutNode?]], + alignment: Alignment, + columnAlignments: [HorizontalAlignment?], + rowAlignments: [VerticalAlignment?], + verticalSpacing: CGFloat, + horizontalSpacing: CGFloat, + rowCount: Int, + columnCount: Int +) -> LayoutNode { + let isRTL = LayoutContext.layoutDirection == .rightToLeft + var rowSizes = Array(repeating: CGFloat(0), count: rowCount) + var columnSizes = Array(repeating: CGFloat(0), count: columnCount) + for (rowIndex, rowLayouts) in layouts.enumerated() { + for (columnIndex, layout) in rowLayouts.enumerated() { + if let layout { + rowSizes[rowIndex] = max(rowSizes[rowIndex], layout.size.height) + columnSizes[columnIndex] = max(columnSizes[columnIndex], layout.size.width) + } + } + } + var positionedChildren = [LayoutNode.Child]() + var currentY: CGFloat = 0 + for (rowIndex, rowLayouts) in layouts.enumerated() { + var currentX: CGFloat = 0 + let rowAlignment = rowAlignments[rowIndex] + let range = { + if isRTL { + stride(from: rowLayouts.count - 1, through: 0, by: -1) + } else { + stride(from: 0, through: rowLayouts.count - 1, by: 1) + } + }() + for columnIndex in range { + if let layout = rowLayouts[columnIndex] { + let columnAlignmentOverride = columnAlignments[columnIndex] + let cellSize = CGSize(width: columnSizes[columnIndex], height: rowSizes[rowIndex]) + let dimensions = ElementDimensions(cellSize) + let xPosition: CGFloat + let yPosition: CGFloat + if let unitPoint = find(in: layout, valueBlock: { $0.unitPoint }) { + let unitPoint = isRTL ? UnitPoint(x: 1 - unitPoint.x, y: unitPoint.y) : unitPoint + xPosition = (dimensions.width - layout.size.width) * unitPoint.x + yPosition = (dimensions.height - layout.size.height) * unitPoint.y + } else { + let gridCellAnchorAlignment = find(in: layout, valueBlock: { $0.alignment }) + let alignment = + gridCellAnchorAlignment + ?? Alignment( + horizontal: columnAlignmentOverride ?? alignment.horizontal, + vertical: rowAlignment ?? alignment.vertical + ) + xPosition = dimensions[alignment.horizontal] - layout.dimensions[alignment.horizontal] + yPosition = dimensions[alignment.vertical] - layout.dimensions[alignment.vertical] + } + let position = CGPoint(x: currentX + xPosition, y: currentY + yPosition) + positionedChildren.append(LayoutNode.Child(position: roundToPixelGrid(position), layout: layout)) + } + currentX += columnSizes[columnIndex] + horizontalSpacing + } + currentY += rowSizes[rowIndex] + verticalSpacing + } + let totalWidth = columnSizes.reduce(0, +) + CGFloat(columnCount - 1) * horizontalSpacing + let totalHeight = rowSizes.reduce(0, +) + CGFloat(rowCount - 1) * verticalSpacing + let gridSize = CGSize(width: totalWidth, height: totalHeight) + return LayoutNode(view: nil, size: gridSize, children: positionedChildren, alignmentGuides: AlignmentGuidesResolver.none()) +} + +@inline(__always) +private func extractColumnAlignments(from cellLayouts: [[LayoutNode?]], columnCount: Int) -> [HorizontalAlignment?] { + var columnAlignments: [HorizontalAlignment?] = Array(repeating: nil, count: columnCount) + for rowLayouts in cellLayouts { + for (colIndex, layout) in rowLayouts.enumerated() { + if let layout, let columnAlignment = find(in: layout, valueBlock: { $0.columnAlignment }) { + columnAlignments[colIndex] = columnAlignment + } + } + } + return columnAlignments +} + +@inline(__always) +private func extractRowAlignments(from rows: [GridRowElement], rowCount: Int) -> [VerticalAlignment?] { + var rowAlignments: [VerticalAlignment?] = Array(repeating: nil, count: rowCount) + for rowIndex in 0.. Bool { + return Fuzzy.compare(proposedSize.width, lessThanOrEqual: lastProposedSize?.width ?? .infinity) + && Fuzzy.compare(proposedSize.height, lessThanOrEqual: lastProposedSize?.height ?? .infinity) + && Fuzzy.compare(lastLayout.size.width, lessThanOrEqual: proposedSize.width) + && Fuzzy.compare(lastLayout.size.height, lessThanOrEqual: proposedSize.height) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeStackLayout.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeStackLayout.swift new file mode 100644 index 0000000..4d50462 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/ComputeStackLayout.swift @@ -0,0 +1,227 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +@inline(__always) +// Substracts the value. If the result is negative, it returns zero. +private func substract(from main: CGFloat, _ value: CGFloat) -> CGFloat { + max(0.0, main - value) +} + +@inline(__always) +private func layoutGroup(indices: [Int], layouts: inout [LayoutNode], main: CGFloat, cross: CGFloat, mainAxis: Axis, children: [Element], stackItems: inout [StackItem]) -> CGFloat { + var group = indices + let groupCount = indices.count + let groupProposedSize = CGSize(main: main, cross: cross, mainAxis: mainAxis) + + if groupCount == 1 { + /// Measure a single child group immediately and only once. + /// This optimization resolves an issue where a lone child within a group was measured twice due to rounding discrepancies. + /// For instance, when the `groupProposedSize.width` is 233.32426488399506 but the child's width is 233.33333333333334, + /// the algorithm previously remeasured the child unnecessarily. + let index = indices[0] + layouts[index] = children[index].quick_layoutThatFits(groupProposedSize) + return substract(from: main, layouts[index].size.main(for: mainAxis)) + } + + for index in group { + let item = stackItems[index] + if item.premeasure { + let layout = children[index].quick_layoutThatFits(groupProposedSize) + stackItems[index].premeasuredMain = layout.size.main(for: mainAxis) + layouts[index] = layout + } + } + group.sort { stackItems[$0].premeasuredMain < stackItems[$1].premeasuredMain } + + var groupAvailableMain = main + var numChildrenToLayout = groupCount + for index in group { + let item = stackItems[index] + let itemAvailableMain = groupAvailableMain / CGFloat(numChildrenToLayout) // Divide the remaining available space on the main axis equally between all children + if !item.premeasure { + layouts[index] = children[index].quick_layoutThatFits(CGSize(main: itemAvailableMain, cross: cross, mainAxis: mainAxis)) + } else if item.premeasure && Fuzzy.compare(item.premeasuredMain, greaterThan: itemAvailableMain) { + /// If we are here it's a double measurement. + layouts[index] = children[index].quick_layoutThatFits(CGSize(main: itemAvailableMain, cross: cross, mainAxis: mainAxis)) + } + groupAvailableMain = substract(from: groupAvailableMain, layouts[index].size.main(for: mainAxis)) + numChildrenToLayout -= 1 + } + return groupAvailableMain +} + +@inline(__always) +private func shouldRemeasure(_ node: LayoutNode, children: [Element], mainAxis: Axis) -> Bool { + let firstSize: CGFloat = node.children.first?.layout.size.cross(for: mainAxis) ?? -1 + for index in (0...children.count - 1) { + if firstSize != node.children[index].layout.size.cross(for: mainAxis) && !children[index].quickInternal_isSpacer() { + return true + } + } + return false +} + +@inline(__always) +private func newCrossSizeForRemeasure(_ node: LayoutNode, mainAxis: Axis) -> CGFloat { + var newCrossSizeForRemeasure: CGFloat = node.children.first?.layout.size.cross(for: mainAxis) ?? 0 + for child in node.children { + newCrossSizeForRemeasure = max(newCrossSizeForRemeasure, child.layout.size.cross(for: mainAxis)) + } + return newCrossSizeForRemeasure +} + +func computeStackLayout(children: [Element], alignment: AnyAlignmentID, mainAxis: Axis, spacing: CGFloat, proposedSize: CGSize, idealLayout: Bool) -> LayoutNode { + if idealLayout { + var newProposedSize = proposedSize + let originalCross = newProposedSize.cross(for: mainAxis) + newProposedSize = newProposedSize.replaceCross(with: .infinity, mainAxis: mainAxis) + let layout = computeStackLayout(children: children, alignment: alignment, mainAxis: mainAxis, spacing: spacing, proposedSize: newProposedSize) + if !shouldRemeasure(layout, children: children, mainAxis: mainAxis) && Fuzzy.compare(layout.size.cross(for: mainAxis), lessThanOrEqual: originalCross) { + /// If all children have the same cross size, it's the ideal layout. + return layout + } + let newCross = newCrossSizeForRemeasure(layout, mainAxis: mainAxis) + let newSize = CGSize(main: newProposedSize.main(for: mainAxis), cross: min(newCross, originalCross), mainAxis: mainAxis) + return computeStackLayout(children: children, alignment: alignment, mainAxis: mainAxis, spacing: spacing, proposedSize: newSize) + } else { + return computeStackLayout(children: children, alignment: alignment, mainAxis: mainAxis, spacing: spacing, proposedSize: proposedSize) + } +} + +func computeStackLayout(children: [Element], alignment: AnyAlignmentID, mainAxis: Axis, spacing: CGFloat, proposedSize: CGSize) -> LayoutNode { + let childrenCount = children.count + + if childrenCount == 0 { + return LayoutNode.empty + } + + if childrenCount == 1 { + /// This is a special case to avoid unnecessary calculations. + /// The stack measures the single element and acquires its size. + let childLayout = children[0].quick_layoutThatFits(proposedSize) + let childNode = LayoutNode.Child(position: .zero, layout: childLayout) + return LayoutNode(view: nil, size: childLayout.size, children: [childNode], alignmentGuides: AlignmentGuidesResolver.extract(childNode)) + } + + var spacingAfter: [CGFloat]? + if spacing != 0 { + spacingAfter = [CGFloat](repeating: 0.0, count: childrenCount) + } + var lastNonZeroElementIndex = -1 + + let main = proposedSize.main(for: mainAxis) + let cross = proposedSize.cross(for: mainAxis) + var stackAvailableMain = main + + // Groups children by their layout priority. Children with the same priority are sorted by their premeasured main size. + // Fixed size children are not grouped, they are measured immediately. + var groupedIndices = [CGFloat: [Int]]() + var layouts = [LayoutNode]() + var stackItems = [StackItem]() + + layouts.reserveCapacity(childrenCount) + stackItems.reserveCapacity(childrenCount) + + for (index, child) in children.enumerated() { + let mainAxisFlexibility = child.quick_flexibility(for: mainAxis) + let sizedToZero: Bool + switch mainAxisFlexibility { + case .fixedSize: + let size = CGSize(main: stackAvailableMain, cross: cross, mainAxis: mainAxis) + let layout = child.quick_layoutThatFits(size) + let item = StackItem.empty + sizedToZero = layout.size.height == 0 && layout.size.width == 0 + + /// Subtract the total amount of spacing between children from the space available on the main axis + stackAvailableMain = substract(from: stackAvailableMain, layout.size.main(for: mainAxis)) + layouts.append(layout) + stackItems.append(item) + case .partial, .fullyFlexible: + /// The spacing will be added even if the child is sized to zero. It's not ideal, but it could only be fixed with a double measurement pass, which I want to avoid. + sizedToZero = false + let layoutPriority = child.quick_layoutPriority() + let item = StackItem( + premeasure: mainAxisFlexibility == .partial, + premeasuredMain: mainAxisFlexibility == .fullyFlexible ? .infinity : 0 + ) + layouts.append(LayoutNode.empty) + stackItems.append(item) + groupedIndices[layoutPriority, default: [Int]()].append(index) + } + + if spacing != 0.0 { + /// The spacing is added only between two non Spacer elements if they are not pre-sized to zero. + /// + /// If a Spacer separates two elements, no spacing is added. See examples below + /// [Element1, Spacer(), Element2] + /// [Element1, Spacer(x), Spacer(), Element2] + /// + /// If two elements are separated by zero sized elements, such as EmptyLayout or empty stack, only one spacing is added + /// [Element1, EmptyLayout(), Element2] -> [Element1, spacing, {0,0}, Element2] + /// [Element1, EmptyLayout(), EmptyLayout(), Element2] -> [Element1, spacing, {0,0}, {0,0}, Element2] + /// [Element1, HStack {}, Element2] -> [Element1, spacing, {0,0}, Element2] + let isCurrentChildSpacer = child.quickInternal_isSpacer() + if lastNonZeroElementIndex >= 0 { + let shouldInsertSpacing = !sizedToZero && !isCurrentChildSpacer && !children[lastNonZeroElementIndex].quickInternal_isSpacer() + let spacingToInsert = shouldInsertSpacing ? spacing : 0.0 + spacingAfter![lastNonZeroElementIndex] = spacingToInsert // swiftlint:disable:this force_unwrapping + stackAvailableMain = substract(from: stackAvailableMain, spacingToInsert) + } + if isCurrentChildSpacer || !sizedToZero { + lastNonZeroElementIndex = index + } + } + } + + for key in groupedIndices.keys.sorted(by: >) { + if let group = groupedIndices[key] { + stackAvailableMain = layoutGroup(indices: group, layouts: &layouts, main: stackAvailableMain, cross: cross, mainAxis: mainAxis, children: children, stackItems: &stackItems) + } + } + + let rtlLayout = LayoutContext.layoutDirection == .rightToLeft && mainAxis == .horizontal + let range = rtlLayout ? stride(from: childrenCount - 1, through: 0, by: -1) : stride(from: 0, through: childrenCount - 1, by: 1) + + var maxAlignmentGuideLength = 0.0 + for index in range { + maxAlignmentGuideLength = max(maxAlignmentGuideLength, layouts[index].dimensions[alignment]) + } + + var caret = 0.0 // Calculate positions on the main axis starting with zero and then adding the main size of each child as well as spacing + var resultFrame = CGRect.zero // Calculate the size of the whole stack as the union of all children frames + var childNodes: [LayoutNode.Child] = range.map { index in + let layout = layouts[index] + let mainAxisPos = caret + + let spacing: CGFloat + if rtlLayout { + spacing = index > 0 ? spacingAfter?[index - 1] ?? 0.0 : 0.0 + } else { + spacing = spacingAfter?[index] ?? 0.0 + } + caret += layout.size.main(for: mainAxis) + spacing + + // Position on the cross axis is the difference between the length of the alignment guide for a particular child and the maximum length. The child with the maximum length of the alignment guide will thus be placed flush with the start edge of the cross axis + let alignmentGuideLength = layout.dimensions[alignment] + let crossAxisPos = maxAlignmentGuideLength - alignmentGuideLength + let position = roundToPixelGrid(CGPoint(main: mainAxisPos, cross: crossAxisPos, mainAxis: mainAxis)) + + let childSize = layout.size + resultFrame = resultFrame.union(CGRect(origin: position, size: childSize)) + + return LayoutNode.Child(position: position, layout: layout) + } + + if rtlLayout { + childNodes.reverse() + } + + let stackSize = CGSize(width: roundPositionToPixelGrid(resultFrame.width), height: roundPositionToPixelGrid(resultFrame.height)) + return LayoutNode(view: nil, size: stackSize, children: childNodes, alignmentGuides: AlignmentGuidesResolver.extract(for: childNodes)) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/CoreGraphicsExtensions.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/CoreGraphicsExtensions.swift new file mode 100644 index 0000000..6b85fe3 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/CoreGraphicsExtensions.swift @@ -0,0 +1,47 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import CoreGraphics + +extension CGPoint { + init(main: CGFloat, cross: CGFloat, mainAxis: Axis) { + switch mainAxis { + case .horizontal: self.init(x: main, y: cross) + case .vertical: self.init(x: cross, y: main) + } + } +} + +extension CGSize { + init(main: CGFloat, cross: CGFloat, mainAxis: Axis) { + switch mainAxis { + case .horizontal: self.init(width: main, height: cross) + case .vertical: self.init(width: cross, height: main) + } + } + + func replaceCross(with value: CGFloat, mainAxis: Axis) -> CGSize { + switch mainAxis { + case .horizontal: CGSize(width: width, height: value) + case .vertical: CGSize(width: value, height: height) + } + } + + func main(for axis: Axis) -> CGFloat { + switch axis { + case .horizontal: width + case .vertical: height + } + } + + func cross(for axis: Axis) -> CGFloat { + switch axis { + case .horizontal: height + case .vertical: width + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/StackItem.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/StackItem.swift new file mode 100644 index 0000000..74431d8 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutAlgorithms/StackItem.swift @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +struct StackItem { + static let empty = StackItem() + + let premeasure: Bool + var premeasuredMain: CGFloat + + init( + premeasure: Bool = false, + premeasuredMain: CGFloat = .infinity + ) { + self.premeasure = premeasure + self.premeasuredMain = premeasuredMain + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutContext.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutContext.swift new file mode 100644 index 0000000..9633ff8 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutContext.swift @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +public struct LayoutContext { + + @TaskLocal + public static var layoutDirection: LayoutDirection = DefaultLayoutDirection.value + + @TaskLocal + /// The main axis of nearest parent Stack. Used by Spacers to determine the main axis to grow. + static var latestMainAxis: Axis = Axis.vertical +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/AlignmentGuideElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/AlignmentGuideElement.swift new file mode 100644 index 0000000..dd7faff --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/AlignmentGuideElement.swift @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct AlignmentGuideElement: Layout { + private let child: Element + private let alignmentID: AnyAlignmentID + private let computeValue: @Sendable (ElementDimensions) -> CGFloat + + public init(child: Element, horizontalAlignment: HorizontalAlignment, computeValue: @escaping @Sendable (ElementDimensions) -> CGFloat) { + self.init(child: child, alignmentID: horizontalAlignment.alignmentID, computeValue: computeValue) + } + + public init(child: Element, verticalAlignment: VerticalAlignment, computeValue: @escaping @Sendable (ElementDimensions) -> CGFloat) { + self.init(child: child, alignmentID: verticalAlignment.alignmentID, computeValue: computeValue) + } + + init(child: Element, alignmentID: AnyAlignmentID, computeValue: @escaping @Sendable (ElementDimensions) -> CGFloat) { + self.child = child + self.alignmentID = alignmentID + self.computeValue = computeValue + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let childLayout = child.quick_layoutThatFits(proposedSize) + let childNode = LayoutNode.Child(position: .zero, layout: childLayout) + + var alignmentGuides = AlignmentGuidesResolver.extract(childNode) + alignmentGuides[alignmentID] = computeValue + + return LayoutNode( + view: nil, + size: childLayout.size, + children: [childNode], + alignmentGuides: alignmentGuides + ) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/AspectRatioElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/AspectRatioElement.swift new file mode 100644 index 0000000..cc58f38 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/AspectRatioElement.swift @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public enum ContentMode { + case fill + case fit +} + +public struct AspectRatioElement: Layout { + + private let child: Element + private let aspectRatio: CGFloat + private let contentMode: ContentMode + + public init(child: Element, aspectRatio: CGFloat, contentMode: ContentMode) { + self.child = child + self.contentMode = contentMode + self.aspectRatio = aspectRatio + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + .partial + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let newProposedSized = calculateNewSize(proposedSize, aspectRatio: aspectRatio, contentMode: contentMode) + let childLayout = child.quick_layoutThatFits(newProposedSized) + let size = CGSize( + width: childLayout.size.width.isInfinite ? newProposedSized.width : childLayout.size.width, + height: childLayout.size.height.isInfinite ? newProposedSized.height : childLayout.size.height + ) + let childNode = LayoutNode.Child(position: .zero, layout: childLayout) + let alignmentGuides = AlignmentGuidesResolver.extract(childNode) + return LayoutNode(view: nil, size: size, children: [childNode], alignmentGuides: alignmentGuides) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} + +private func calculateNewSize(_ proposedSize: CGSize, aspectRatio: CGFloat, contentMode: ContentMode) -> CGSize { + if aspectRatio <= 0 || !aspectRatio.isFinite { + return proposedSize + } + + let isWidthFinite = proposedSize.width.isFinite + let isHeightFinite = proposedSize.height.isFinite + + if !isWidthFinite && !isHeightFinite { + return proposedSize + } + + if !isWidthFinite { + return CGSize(width: proposedSize.height * CGFloat(aspectRatio), height: proposedSize.height) + } + + if !isHeightFinite { + return CGSize(width: proposedSize.width, height: proposedSize.width / CGFloat(aspectRatio)) + } + + let proposedAspectRatio = proposedSize.width / proposedSize.height + + switch contentMode { + case .fill: + if proposedAspectRatio > aspectRatio { + return CGSize(width: proposedSize.width, height: proposedSize.width / CGFloat(aspectRatio)) + } else { + return CGSize(width: proposedSize.height * CGFloat(aspectRatio), height: proposedSize.height) + } + case .fit: + if proposedAspectRatio > aspectRatio { + return CGSize(width: proposedSize.height * CGFloat(aspectRatio), height: proposedSize.height) + } else { + return CGSize(width: proposedSize.width, height: proposedSize.width / CGFloat(aspectRatio)) + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/BaseLayout.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/BaseLayout.swift new file mode 100644 index 0000000..e0d0ec4 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/BaseLayout.swift @@ -0,0 +1,94 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +extension Layout { + + public func sizeThatFits(_ proposedSize: CGSize) -> CGSize { + quick_layoutThatFits(sanitizeSize(proposedSize)).size + } + + public func layoutThatFits(_ proposedSize: CGSize, layoutDirection: LayoutDirection? = nil) -> LayoutNode { + let layoutDirection = layoutDirection ?? LayoutContext.layoutDirection + return LayoutContext.$layoutDirection.withValue(layoutDirection) { + quick_layoutThatFits(sanitizeSize(proposedSize)) + } + } + + @MainActor + public func applyFrame(_ frame: CGRect, alignment: Alignment, layoutDirection: LayoutDirection?) { + _applyFrame(frame, alignment: alignment, layoutDirection: layoutDirection, cachedLayout: nil) + } + + @MainActor + public func _applyFrame(_ frame: CGRect, alignment: Alignment, layoutDirection: LayoutDirection?, cachedLayout: LayoutNode?) { + let layoutDirection = layoutDirection ?? LayoutContext.layoutDirection + LayoutContext.$layoutDirection.withValue(layoutDirection) { + let sanitizedFrame = sanitizeFrame(frame) + let dimensions = ElementDimensions(sanitizedFrame.size) + let layout = cachedLayout ?? quick_layoutThatFits(sanitizedFrame.size) + let x = sanitizedFrame.size.width.isInfinite ? 0.0 : roundPositionToPixelGrid(dimensions[alignment.horizontal] - layout.dimensions[alignment.horizontal]) + let y = sanitizedFrame.size.height.isInfinite ? 0.0 : roundPositionToPixelGrid(dimensions[alignment.vertical] - layout.dimensions[alignment.vertical]) + + commitLayout( + child: LayoutNode.Child(position: .zero, layout: layout), + position: CGPoint(x: sanitizedFrame.origin.x + x, y: sanitizedFrame.origin.y + y) + ) + } + } + + public func views() -> [UIView] { + var views: [UIView] = [] + quick_extractViewsIntoArray(&views) + return views + } +} + +private func sanitizePosition(_ value: CGFloat) -> CGFloat { + if value.isNaN { return 0.0 } + return value +} + +// Preventing from passing NaN and negative sizes to the layout. Replacing CGFloat.greatestFiniteMagnitude with infinity. +private func sanitizeDimension(_ value: CGFloat) -> CGFloat { + if value.isNaN { return 0.0 } + if value == CGFloat.greatestFiniteMagnitude { return .infinity } + return max(value, 0.0) +} + +private func sanitizeSize(_ size: CGSize) -> CGSize { + CGSize(width: sanitizeDimension(size.width), height: sanitizeDimension(size.height)) +} + +// Preventing from passing NaNs and negatives to the layout. Though allowing infinite sizes. +private func sanitizeFrame(_ input: CGRect) -> CGRect { + CGRect(x: sanitizePosition(input.origin.x), y: sanitizePosition(input.origin.y), width: sanitizeDimension(input.width), height: sanitizeDimension(input.height)) +} + +@MainActor +private func commitLayout(child: LayoutNode.Child, position: CGPoint) { + let origin = CGPoint(x: position.x + child.position.x, y: position.y + child.position.y) + if let view = child.layout.view { + let size = CGSize(width: child.layout.size.width, height: child.layout.size.height) + set(origin: origin, size: size, for: view) + } + child.layout.children.forEach { child in + commitLayout(child: child, position: origin) + } +} + +/// Not using the "frame" property because changing it resets the transform of the view. +/// The method sets "center" and "bounds" and lets UIKit resolve the frame with the current view transform. +/// See the diff description for more details. D62157627 +@MainActor +private func set(origin: CGPoint, size: CGSize, for view: UIView) { + let anchorPoint = view.layer.anchorPoint + view.bounds = CGRect(origin: view.bounds.origin, size: size) + view.center = CGPoint(x: origin.x + size.width * anchorPoint.x, y: origin.y + size.height * anchorPoint.y) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/EmptyElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/EmptyElement.swift new file mode 100644 index 0000000..df04d8c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/EmptyElement.swift @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct EmptyElement: Layout { + + public init() {} + + public func quick_flexibility(for axis: Axis) -> Flexibility { + .fixedSize + } + + public func quick_layoutPriority() -> CGFloat { + CGFloat.infinity + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + LayoutNode.empty + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + // no-op + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ExpandableElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ExpandableElement.swift new file mode 100644 index 0000000..d2b187d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ExpandableElement.swift @@ -0,0 +1,46 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct ExpandableElement: Layout { + + private let child: LeafElement + private let size: CGSize + + public init(child: LeafElement, size: CGSize) { + self.child = child + self.size = sanitizeInputSize(size) + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let childLayout = child.quick_layoutThatFits(proposedSize) + let expandedSize = CGSize(width: size.width + childLayout.size.width, height: size.height + childLayout.size.height) + return LayoutNode(view: childLayout.view, dimensions: ElementDimensions(sanitizeOutputSize(expandedSize))) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} + +private func sanitizeInputSize(_ size: CGSize) -> CGSize { + CGSize(width: size.width.isFinite ? size.width : 0, height: size.height.isFinite ? size.height : 0) +} + +private func sanitizeOutputSize(_ size: CGSize) -> CGSize { + CGSize(width: max(size.width, 0), height: max(size.height, 0)) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FixedFrameElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FixedFrameElement.swift new file mode 100644 index 0000000..e28d4ae --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FixedFrameElement.swift @@ -0,0 +1,66 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct FixedFrameElement: Layout { + + private let child: Element + private let width: CGFloat? + private let height: CGFloat? + private let alignment: Alignment + + public init( + child: Element, + width: CGFloat? = nil, + height: CGFloat? = nil, + alignment: Alignment = .center + ) { + self.child = child + self.width = sanitizedFrameDimension(width) + self.height = sanitizedFrameDimension(height) + self.alignment = alignment + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + switch axis { + case .horizontal: width != nil ? .fixedSize : child.quick_flexibility(for: axis) + case .vertical: height != nil ? .fixedSize : child.quick_flexibility(for: axis) + } + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let proposedSize = CGSize( + width: width ?? proposedSize.width, + height: height ?? proposedSize.height + ) + let layout = child.quick_layoutThatFits(proposedSize) + + let size = CGSize(width: width ?? layout.size.width, height: height ?? layout.size.height) + let dimensions = ElementDimensions(size) + + let x = dimensions[alignment.horizontal] - layout.dimensions[alignment.horizontal] + let y = dimensions[alignment.vertical] - layout.dimensions[alignment.vertical] + let childNode = LayoutNode.Child(position: roundToPixelGrid(CGPoint(x: x, y: y)), layout: layout) + let alignmentGuides = AlignmentGuidesResolver.extract(childNode) + return LayoutNode(view: nil, size: size, children: [childNode], alignmentGuides: alignmentGuides) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} + +private func sanitizedFrameDimension(_ width: CGFloat?) -> CGFloat? { + guard let width, width.isFinite else { return nil } + return max(width, 0) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FixedSizeElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FixedSizeElement.swift new file mode 100644 index 0000000..5a9b6c6 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FixedSizeElement.swift @@ -0,0 +1,45 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct FixedSizeElement: Layout { + + private let child: Element + private let horizontal: Bool + private let vertical: Bool + + public init(child: Element, horizontal: Bool, vertical: Bool) { + self.child = child + self.horizontal = horizontal + self.vertical = vertical + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + switch axis { + case .horizontal: horizontal ? .fixedSize : child.quick_flexibility(for: axis) + case .vertical: vertical ? .fixedSize : child.quick_flexibility(for: axis) + } + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let newProposedSize = CGSize( + width: horizontal ? .infinity : proposedSize.width, + height: vertical ? .infinity : proposedSize.height + ) + return child.quick_layoutThatFits(newProposedSize) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FlexibleFrameElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FlexibleFrameElement.swift new file mode 100644 index 0000000..bec2773 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FlexibleFrameElement.swift @@ -0,0 +1,110 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct FlexibleFrameElement: Layout { + + private let child: Element + private let minWidth: CGFloat? + private let maxWidth: CGFloat? + private let minHeight: CGFloat? + private let maxHeight: CGFloat? + private let alignment: Alignment + + public init( + child: Element, + minWidth: CGFloat?, + maxWidth: CGFloat?, + minHeight: CGFloat?, + maxHeight: CGFloat?, + alignment: Alignment = .center + ) { + self.child = child + self.minWidth = sanitizedFrameDimension(minWidth) + self.maxWidth = sanitizedFrameDimension(maxWidth) + self.minHeight = sanitizedFrameDimension(minHeight) + self.maxHeight = sanitizedFrameDimension(maxHeight) + self.alignment = alignment + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + if axis == .horizontal && maxWidth != nil { + return .partial + } + if axis == .vertical && maxHeight != nil { + return .partial + } + return child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let clampedProposedSize = CGSize( + width: clamp(proposedSize.width, minWidth, maxWidth), + height: clamp(proposedSize.height, minHeight, maxHeight) + ) + let layout = child.quick_layoutThatFits(clampedProposedSize) + + let size = CGSize( + width: frameSize(childSize: layout.size.width, proposedSizeByParent: proposedSize.width, minFrameConstraint: minWidth, maxFrameConstraint: maxWidth), + height: frameSize(childSize: layout.size.height, proposedSizeByParent: proposedSize.height, minFrameConstraint: minHeight, maxFrameConstraint: maxHeight) + ) + + let dimensions = ElementDimensions(size) + + let x = dimensions[alignment.horizontal] - layout.dimensions[alignment.horizontal] + let y = dimensions[alignment.vertical] - layout.dimensions[alignment.vertical] + let childNode = LayoutNode.Child(position: roundToPixelGrid(CGPoint(x: x, y: y)), layout: layout) + let alignmentGuides = AlignmentGuidesResolver.extract(childNode) + return LayoutNode(view: nil, size: size, children: [childNode], alignmentGuides: alignmentGuides) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} + +private func sanitizedFrameDimension(_ width: CGFloat?) -> CGFloat? { + guard let width else { return nil } + return max(width, 0) +} + +private func clamp(_ f: CGFloat, _ minNumber: CGFloat?, _ maxNumber: CGFloat?) -> CGFloat { + let minValue = minNumber ?? 0 + let maxValue = maxNumber ?? .infinity + return min(max(f, minValue), maxValue) +} + +private func frameSize(childSize: CGFloat, proposedSizeByParent: CGFloat, minFrameConstraint: CGFloat?, maxFrameConstraint: CGFloat?) -> CGFloat { + + if minFrameConstraint == nil && maxFrameConstraint == nil { + return childSize + } + + if let minFrameConstraint, let maxFrameConstraint { + if proposedSizeByParent.isFinite || maxFrameConstraint.isFinite { + return clamp(proposedSizeByParent, minFrameConstraint, maxFrameConstraint) + } + } + + if let minFrameConstraint, proposedSizeByParent < childSize { + return max(proposedSizeByParent, minFrameConstraint) + } + + if let maxFrameConstraint, proposedSizeByParent > childSize { + if proposedSizeByParent.isFinite || maxFrameConstraint.isFinite { + return min(proposedSizeByParent, maxFrameConstraint) + } + } + + return clamp(childSize, minFrameConstraint, maxFrameConstraint) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FlowElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FlowElement.swift new file mode 100644 index 0000000..ecf964c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/FlowElement.swift @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct FlowElement: Layout { + + private let children: [Element] + private let itemSpacing: CGFloat + private let lineSpacing: CGFloat + private let mainAxis: Axis + private let itemAlignmentID: AnyAlignmentID + private let lineAlignmentID: AnyAlignmentID + + public init(children: [Element], mainAxis: Axis, itemSpacing: CGFloat, lineSpacing: CGFloat, itemAlignmentID: AnyAlignmentID, lineAlignmentID: AnyAlignmentID) { + self.itemSpacing = sanitizeSpacing(itemSpacing) + self.lineSpacing = sanitizeSpacing(lineSpacing) + self.children = children.filter { !$0.quickInternal_isSpacer() } + self.mainAxis = mainAxis + self.itemAlignmentID = itemAlignmentID + self.lineAlignmentID = lineAlignmentID + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + LayoutContext.$latestMainAxis.withValue(mainAxis) { + computeFlowLayout( + proposedSize: proposedSize, + children: children, + itemSpacing: itemSpacing, + lineSpacing: lineSpacing, + mainAxis: mainAxis, + itemAlignment: itemAlignmentID, + lineAlignment: lineAlignmentID + ) + } + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + return .fixedSize + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + children.forEach { child in + child.quick_extractViewsIntoArray(&views) + } + } +} + +private func sanitizeSpacing(_ spacing: CGFloat) -> CGFloat { + spacing.isFinite ? spacing : 0 +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridCellAnchorElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridCellAnchorElement.swift new file mode 100644 index 0000000..6dbe7a4 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridCellAnchorElement.swift @@ -0,0 +1,48 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct GridCellAnchorElement: Layout { + + private let child: Element + public let alignment: Alignment? + public let unitPoint: UnitPoint? + + public init(child: Element, alignment: Alignment? = nil, unitPoint: UnitPoint? = nil) { + self.child = child + self.alignment = alignment + self.unitPoint = unitPoint + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let childLayout = child.quick_layoutThatFits(proposedSize) + + let gridInfo = GridInfo(alignment: alignment, unitPoint: unitPoint, columnAlignment: nil) + + return LayoutNode( + view: childLayout.view, + dimensions: childLayout.dimensions, + gridInfo: gridInfo, + children: childLayout.children + ) + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + return child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } + +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridColumnAlignmentElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridColumnAlignmentElement.swift new file mode 100644 index 0000000..b6dd294 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridColumnAlignmentElement.swift @@ -0,0 +1,45 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct GridColumnAlignmentElement: Layout { + + private let child: Element + public let alignment: HorizontalAlignment + + public init(child: Element, alignment: HorizontalAlignment) { + self.alignment = alignment + self.child = child + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let childLayout = child.quick_layoutThatFits(proposedSize) + + let gridInfo = GridInfo(alignment: nil, unitPoint: nil, columnAlignment: alignment) + + return LayoutNode( + view: childLayout.view, + dimensions: childLayout.dimensions, + gridInfo: gridInfo, + children: childLayout.children + ) + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + return child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridElement.swift new file mode 100644 index 0000000..684236c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/GridElement.swift @@ -0,0 +1,73 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct GridElement: Layout { + + private let rows: [GridRowElement] + private let alignment: Alignment + private let horizontalSpacing: CGFloat + private let verticalSpacing: CGFloat + + public init(rows: [GridRowElement], alignment: Alignment, horizontalSpacing: CGFloat, verticalSpacing: CGFloat) { + self.rows = rows + self.alignment = alignment + self.horizontalSpacing = sanitizeSpacing(horizontalSpacing) + self.verticalSpacing = sanitizeSpacing(verticalSpacing) + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + var maxFlexibility = Flexibility.fixedSize + for row in rows { + for child in row.children { + let childFlex = child.quick_flexibility(for: axis) + if childFlex.rawValue > maxFlexibility.rawValue { + maxFlexibility = childFlex + } + if maxFlexibility == .fullyFlexible { + return maxFlexibility + } + } + } + return maxFlexibility + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + return computeGridLayout(rows: rows, alignment: alignment, proposedSize: proposedSize, verticalSpacing: verticalSpacing, horizontalSpacing: horizontalSpacing) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + rows.forEach { row in + row.children.forEach { $0.quick_extractViewsIntoArray(&views) } + } + } +} + +public struct GridRowElement { + + public let children: [Element] + public let alignment: VerticalAlignment? + + public init(children: [Element], alignment: VerticalAlignment?) { + self.children = children + self.alignment = alignment + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } +} + +private func sanitizeSpacing(_ spacing: CGFloat) -> CGFloat { + (spacing.isFinite && spacing > 0) ? spacing : 0 +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayeringElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayeringElement.swift new file mode 100644 index 0000000..cac0107 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayeringElement.swift @@ -0,0 +1,77 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public enum LayeringType { + case background + case overlay +} + +public struct LayeringElement: Layout { + + private let type: LayeringType + private let target: Element + private let layer: Element + private let alignment: Alignment + + public init( + target: Element, + layer: Element, + type: LayeringType, + alignment: Alignment = .center + ) { + self.type = type + self.target = target + self.layer = layer + self.alignment = alignment + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + target.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + target.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let targetLayout = target.quick_layoutThatFits(proposedSize) + let layerLayout = layer.quick_layoutThatFits(targetLayout.size) + + let x = targetLayout.dimensions[alignment.horizontal] - layerLayout.dimensions[alignment.horizontal] + let y = targetLayout.dimensions[alignment.vertical] - layerLayout.dimensions[alignment.vertical] + let targetChild = LayoutNode.Child(position: .zero, layout: targetLayout) + let alignmentGuides = AlignmentGuidesResolver.extract(targetChild) + + return LayoutNode( + view: nil, + size: targetLayout.size, + children: [ + targetChild, + LayoutNode.Child(position: roundToPixelGrid(CGPoint(x: x, y: y)), layout: layerLayout), + ], + alignmentGuides: alignmentGuides + ) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + switch type { + case .background: + layer.quick_extractViewsIntoArray(&views) + target.quick_extractViewsIntoArray(&views) + case .overlay: + target.quick_extractViewsIntoArray(&views) + layer.quick_extractViewsIntoArray(&views) + } + } +} + +private func sanitizePoint(_ p: CGPoint) -> CGPoint { + CGPoint(x: p.x.isNaN ? 0.0 : p.x, y: p.y.isNaN ? 0.0 : p.y) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayoutDirectionElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayoutDirectionElement.swift new file mode 100644 index 0000000..39615f9 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayoutDirectionElement.swift @@ -0,0 +1,40 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct LayoutDirectionElement: Layout { + + private let child: Element + private let layoutDirection: LayoutDirection + + public init(child: Element, layoutDirection: LayoutDirection) { + self.child = child + self.layoutDirection = layoutDirection + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + LayoutContext.$layoutDirection.withValue(layoutDirection) { + child.quick_layoutThatFits(proposedSize) + } + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + LayoutContext.$layoutDirection.withValue(layoutDirection) { + child.quick_flexibility(for: axis) + } + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayoutPriorityElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayoutPriorityElement.swift new file mode 100644 index 0000000..28741ba --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/LayoutPriorityElement.swift @@ -0,0 +1,36 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct LayoutPriorityElement: Element { + + private let child: Element + private let layoutPriority: CGFloat + + public init(child: Element, layoutPriority: CGFloat) { + self.child = child + self.layoutPriority = layoutPriority + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + layoutPriority + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + child.quick_layoutThatFits(proposedSize) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/OffsetElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/OffsetElement.swift new file mode 100644 index 0000000..7f909f7 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/OffsetElement.swift @@ -0,0 +1,47 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct OffsetElement: Layout { + + private let child: Element + private let offset: CGPoint + + public init(child: Element, offset: CGPoint) { + self.child = child + self.offset = sanitizeOffset(offset) + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + var offset = self.offset + if LayoutContext.layoutDirection == .rightToLeft { + offset = CGPoint(x: -offset.x, y: offset.y) + } + let childLayout = child.quick_layoutThatFits(proposedSize) + let childNode = LayoutNode.Child(position: offset, layout: childLayout) + let alignmentGuides = AlignmentGuidesResolver.extract(childNode) + return LayoutNode(view: nil, size: childLayout.size, children: [childNode], alignmentGuides: alignmentGuides) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} + +private func sanitizeOffset(_ offset: CGPoint) -> CGPoint { + CGPoint(x: offset.x.isFinite ? offset.x : 0, y: offset.y.isFinite ? offset.y : 0) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/PaddingElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/PaddingElement.swift new file mode 100644 index 0000000..1c6c31f --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/PaddingElement.swift @@ -0,0 +1,103 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct PaddingElement: Layout { + + private let child: Element + private let insets: UIEdgeInsets + private let respectLayoutDirection: Bool + + public init(child: Element, insets: UIEdgeInsets) { + self.respectLayoutDirection = false + self.insets = sanitizeInsets(insets) + self.child = child + } + + public init(child: Element, value: CGFloat) { + self.respectLayoutDirection = false + self.insets = sanitizedInsets(value) + self.child = child + } + + public init(child: Element, horizontal: CGFloat, vertical: CGFloat) { + self.respectLayoutDirection = false + self.insets = sanitizedInsets(h: horizontal, v: vertical) + self.child = child + } + + public init(child: Element, edges: EdgeSet, length: CGFloat) { + let insets = EdgeInsets( + top: edges.contains(.top) ? length : 0, + leading: edges.contains(.leading) ? length : 0, + bottom: edges.contains(.bottom) ? length : 0, + trailing: edges.contains(.trailing) ? length : 0 + ) + self.init(child: child, insets: insets) + } + + public init(child: Element, insets: EdgeInsets) { + let insets = UIEdgeInsets(top: insets.top, left: insets.leading, bottom: insets.bottom, right: insets.trailing) + self.respectLayoutDirection = true + self.insets = sanitizeInsets(insets) + self.child = child + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + var insets = self.insets + if respectLayoutDirection && LayoutContext.layoutDirection == .rightToLeft { + insets = UIEdgeInsets(top: insets.top, left: insets.right, bottom: insets.bottom, right: insets.left) + } + let childProposedSize = CGSize( + width: max(proposedSize.width - insets.left - insets.right, 0), + height: max(proposedSize.height - insets.top - insets.bottom, 0) + ) + let childLayout = child.quick_layoutThatFits(childProposedSize) + let selfSize = CGSize( + width: childLayout.size.width + insets.left + insets.right, + height: childLayout.size.height + insets.top + insets.bottom + ) + + let childNode = LayoutNode.Child(position: CGPoint(x: insets.left, y: insets.top), layout: childLayout) + let alignmentGuides = AlignmentGuidesResolver.extract(childNode) + return LayoutNode(view: nil, size: selfSize, children: [childNode], alignmentGuides: alignmentGuides) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} + +private func sanitizeInsets(_ insets: UIEdgeInsets) -> UIEdgeInsets { + UIEdgeInsets( + top: insets.top.isFinite ? insets.top : 0, + left: insets.left.isFinite ? insets.left : 0, + bottom: insets.bottom.isFinite ? insets.bottom : 0, + right: insets.right.isFinite ? insets.right : 0 + ) +} + +private func sanitizedInsets(_ value: CGFloat) -> UIEdgeInsets { + let value = value.isFinite ? value : 0 + return UIEdgeInsets(top: value, left: value, bottom: value, right: value) +} + +private func sanitizedInsets(h: CGFloat, v: CGFloat) -> UIEdgeInsets { + let h = h.isFinite ? h : 0 + let v = v.isFinite ? v : 0 + return UIEdgeInsets(top: v, left: h, bottom: v, right: h) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ResizableElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ResizableElement.swift new file mode 100644 index 0000000..e2745b5 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ResizableElement.swift @@ -0,0 +1,61 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct ResizableElement: Layout { + + private let child: LeafElement + private let resizableAxis: AxisSet + + public init(child: LeafElement, axis: AxisSet) { + self.child = child + self.resizableAxis = axis + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + if axis == .horizontal && resizableAxis.contains(.horizontal) { + return .fullyFlexible + } + if axis == .vertical && resizableAxis.contains(.vertical) { + return .fullyFlexible + } + return child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let isHorizontal = resizableAxis.contains(.horizontal) + let isVertical = resizableAxis.contains(.vertical) + if isHorizontal && isVertical && proposedSize.width.isFinite && proposedSize.height.isFinite { + let size = sanitizeSize(CGSize(width: proposedSize.width, height: proposedSize.height)) + return LayoutNode(view: child.backingView(), dimensions: ElementDimensions(size)) + } + + let childLayout = child.quick_layoutThatFits(proposedSize) + var childSize = childLayout.size + if isHorizontal && proposedSize.width.isFinite { + childSize.width = proposedSize.width + } + if isVertical && proposedSize.height.isFinite { + childSize.height = proposedSize.height + } + return LayoutNode(view: childLayout.view, dimensions: ElementDimensions(sanitizeSize(childSize))) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} + +private func sanitizeSize(_ s: CGSize) -> CGSize { + CGSize(width: s.width.isFinite ? s.width : 0, height: s.height.isFinite ? s.height : 0) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/SingleElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/SingleElement.swift new file mode 100644 index 0000000..8c7c139 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/SingleElement.swift @@ -0,0 +1,34 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct SingleElement: Layout { + + private let child: Element + + public init(child: Element) { + self.child = child + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + child.quick_flexibility(for: axis) + } + + public func quick_layoutPriority() -> CGFloat { + child.quick_layoutPriority() + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + child.quick_layoutThatFits(proposedSize) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + child.quick_extractViewsIntoArray(&views) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/SpacerElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/SpacerElement.swift new file mode 100644 index 0000000..611a167 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/SpacerElement.swift @@ -0,0 +1,53 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct SpacerElement: Element { + + private let length: CGFloat? + + public init(length: CGFloat? = nil) { + self.length = length + } + + public func quickInternal_isSpacer() -> Bool { + return true + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + if length == nil { + let mainAxis = LayoutContext.latestMainAxis + return axis == mainAxis ? .fullyFlexible : .fixedSize + } + return .fixedSize + } + + public func quick_layoutPriority() -> CGFloat { + length == nil ? -CGFloat.infinity : CGFloat.infinity + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let mainAxis = LayoutContext.latestMainAxis + + let size = + switch mainAxis { + case .horizontal: CGSize(width: length ?? proposedSize.width, height: 0) + case .vertical: CGSize(width: 0, height: length ?? proposedSize.height) + } + return LayoutNode(view: nil, dimensions: ElementDimensions(sanitizeSize(size))) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + // no-op + } +} + +private func sanitizeSize(_ s: CGSize) -> CGSize { + CGSize(width: s.width.isFinite ? s.width : 0, height: s.height.isFinite ? s.height : 0) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/StackElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/StackElement.swift new file mode 100644 index 0000000..c52fd99 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/StackElement.swift @@ -0,0 +1,86 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct StackElement: Layout { + + public let children: [Element] + public let mainAxis: Axis + public let spacing: CGFloat + public let alignmentID: AnyAlignmentID + public let idealLayoutEnabled: Bool + + public static func verticalStack(children: [Element], spacing: CGFloat = 0, alignment: HorizontalAlignment) -> StackElement { + StackElement(children: children, mainAxis: .vertical, spacing: spacing, alignmentID: alignment.alignmentID) + } + + public static func horizontalStack(children: [Element], spacing: CGFloat = 0, alignment: VerticalAlignment) -> StackElement { + StackElement(children: children, mainAxis: .horizontal, spacing: spacing, alignmentID: alignment.alignmentID) + } + + public init(children: [Element], mainAxis: Axis, spacing: CGFloat, alignmentID: AnyAlignmentID, idealLayout: Bool = false) { + self.children = children + self.mainAxis = mainAxis + self.spacing = sanitizeSpacing(spacing) + self.alignmentID = alignmentID + self.idealLayoutEnabled = idealLayout + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + if children.isEmpty { + return .fixedSize + } + + var result = Flexibility.fixedSize + LayoutContext.$latestMainAxis.withValue(mainAxis) { + for child in children { + let flexibility = child.quick_flexibility(for: axis) + if flexibility.rawValue > result.rawValue { + result = flexibility + } + if result == .fullyFlexible { + break + } + } + } + + return result + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + if children.isEmpty { + return LayoutNode.empty + } + + return LayoutContext.$latestMainAxis.withValue(mainAxis) { + computeStackLayout( + children: children, + alignment: alignmentID, + mainAxis: mainAxis, + spacing: spacing, + proposedSize: proposedSize, + idealLayout: idealLayoutEnabled + ) + } + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + children.forEach { child in + child.quick_extractViewsIntoArray(&views) + } + } +} + +private func sanitizeSpacing(_ spacing: CGFloat) -> CGFloat { + spacing.isFinite ? spacing : 0 +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ZStackElement.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ZStackElement.swift new file mode 100644 index 0000000..aaaf3bd --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutPrimitives/ZStackElement.swift @@ -0,0 +1,79 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct ZStackElement: Layout { + + private let children: [Element] + private let alignment: Alignment + + public init( + children: [Element], + alignment: Alignment = .center + ) { + self.children = children + self.alignment = alignment + } + + public func quick_flexibility(for axis: Axis) -> Flexibility { + var result = Flexibility.fixedSize + for child in children { + let flexibility = child.quick_flexibility(for: axis) + if flexibility.rawValue > result.rawValue { + result = flexibility + } + } + return result + } + + public func quick_layoutPriority() -> CGFloat { + 0 + } + + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let childrenCount = children.count + if childrenCount == 0 { + return LayoutNode.empty + } + if childrenCount == 1 { + let childLayout = children[0].quick_layoutThatFits(proposedSize) + let childNode = LayoutNode.Child(position: .zero, layout: childLayout) + return LayoutNode(view: nil, size: childLayout.size, children: [childNode], alignmentGuides: AlignmentGuidesResolver.extract(childNode)) + } + + var maxHorizontalAlignmentGuideLength = -CGFloat.greatestFiniteMagnitude + var maxVerticalAlignmentGuideLength = -CGFloat.greatestFiniteMagnitude + let childLayouts = children.map { child in + let layout = child.quick_layoutThatFits(proposedSize) + maxHorizontalAlignmentGuideLength = max(maxHorizontalAlignmentGuideLength, layout.dimensions[alignment.horizontal]) + maxVerticalAlignmentGuideLength = max(maxVerticalAlignmentGuideLength, layout.dimensions[alignment.vertical]) + return layout + } + + var resultFrame = CGRect.zero + let childNodes: [LayoutNode.Child] = childLayouts.map { layout in + let x = maxHorizontalAlignmentGuideLength - layout.dimensions[alignment.horizontal] + let y = maxVerticalAlignmentGuideLength - layout.dimensions[alignment.vertical] + let childNode = LayoutNode.Child(position: sanitizePoint(CGPoint(x: x, y: y)), layout: layout) + resultFrame = resultFrame.union(CGRect(origin: childNode.position, size: childNode.layout.size)) + return childNode + } + return LayoutNode(view: nil, size: resultFrame.size, children: childNodes, alignmentGuides: AlignmentGuidesResolver.extract(for: childNodes)) + } + + public func quick_extractViewsIntoArray(_ views: inout [UIView]) { + children.forEach { child in + child.quick_extractViewsIntoArray(&views) + } + } +} + +private func sanitizePoint(_ p: CGPoint) -> CGPoint { + CGPoint(x: p.x.isNaN ? 0.0 : p.x, y: p.y.isNaN ? 0.0 : p.y) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/LayoutProtocol.swift b/Sources/QuickLayout/QuickLayoutCore/LayoutProtocol.swift new file mode 100644 index 0000000..144222e --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/LayoutProtocol.swift @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +/// Convienence API to work with the root element. +public protocol Layout: Element { + + /// Calculates the size, taking into account the proposed size and properties of the views or view proxies. + /// + /// **Thread Safety:** + /// - If the layout contains `UIView` instances, this method must be called on the main thread to ensure proper rendering and avoid potential crashes. + /// - If the layout consists only of thread-safe `ViewProxy` objects, this method can be safely invoked from any thread. + /// - However, some `ViewProxy` subclasses, such as `UILabel.proxy`, are not thread-safe. In these cases, calling this method from the main thread is required to prevent unexpected behavior. + /// It's essential to consider the thread safety of your layout components when invoking this method to ensure correct functionality and avoid potential issues. + func sizeThatFits(_ proposedSize: CGSize) -> CGSize + + /// Applies the layout to the containing views. + /// + /// **Thread Safety:** + /// This method must be called on the main thread. + @MainActor + func applyFrame(_ frame: CGRect, alignment: Alignment, layoutDirection: LayoutDirection?) + + func views() -> [UIView] +} + +public extension Layout { + + @MainActor + func applyFrame(_ frame: CGRect) { + applyFrame(frame, alignment: .center, layoutDirection: nil) + } + + @MainActor + func applyFrame(_ frame: CGRect, alignment: Alignment) { + applyFrame(frame, alignment: alignment, layoutDirection: nil) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/PixelGridRounding.swift b/Sources/QuickLayout/QuickLayoutCore/PixelGridRounding.swift new file mode 100644 index 0000000..49b6c81 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/PixelGridRounding.swift @@ -0,0 +1,86 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +private struct ScreenScale { + static let value: CGFloat = { + var scale: CGFloat = 1.0 + if Thread.isMainThread { + MainActor.assumeIsolated { // patternlint-disable-line swift-mainactor-assumeisolated + scale = UIScreen.main.scale + } + } else { + DispatchQueue.main.sync { + scale = UIScreen.main.scale + } + } + return scale + }() +} + +private let _tolerance = 0.0001 + +private enum ValueType { + /// Rounding rule for position. The rounding based on "school book math" with extra rounding tolerance. The value maybe rounded down. + case position + + /// Rounding rule for size. The value is never rounded down. + case size +} + +@inline(__always) +func roundPositionToPixelGrid(_ value: CGFloat, screenScale: CGFloat = ScreenScale.value) -> CGFloat { + round(value, valueType: .position, screenScale: screenScale) +} + +/// Never rounds down. +@inline(__always) +func roundSizeToPixelGrid(_ value: CGFloat, screenScale: CGFloat = ScreenScale.value) -> CGFloat { + round(value, valueType: .size, screenScale: screenScale) +} + +@inline(__always) +func roundToPixelGrid(_ p: CGPoint) -> CGPoint { + let screenScale = ScreenScale.value + return CGPoint( + x: round(p.x, valueType: .position, screenScale: screenScale), + y: round(p.y, valueType: .position, screenScale: screenScale) + ) +} + +@inline(__always) +func roundToPixelGrid(_ s: CGSize) -> CGSize { + let screenScale = ScreenScale.value + return CGSize( + width: round(s.width, valueType: .size, screenScale: screenScale), + height: round(s.height, valueType: .size, screenScale: screenScale) + ) +} + +private func round(_ value: CGFloat, valueType: ValueType, screenScale: CGFloat) -> CGFloat { + let modfValue = modf(value) + let integerPart = modfValue.0 + let fractionalPart = modfValue.1 + if -_tolerance <= fractionalPart && fractionalPart <= _tolerance { + // If fractional part is 0, then no need to round. + return integerPart + } + return integerPart + roundFraction(fractionalPart, valueType: valueType, screenScale: screenScale) +} + +/// Usually rounding is done with ((v * screenScale).rounded() / screenScale), but this method rounds value such as 1.249999 to 1.5 instead of 1. +@inline(__always) +private func roundFraction(_ value: CGFloat, valueType: ValueType, screenScale: CGFloat) -> CGFloat { + let rounding = value > 0 ? _tolerance : -_tolerance + switch valueType { + case .position: return (value * screenScale + rounding).rounded(.toNearestOrAwayFromZero) / screenScale + /// Adding rounding so value like 1.249999 is rounded to 1.5 instead of 1. + case .size: return (value * screenScale).rounded(.awayFromZero) / screenScale // Using awayFromZero to avoid rounding down. + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/AlignmentID.swift b/Sources/QuickLayout/QuickLayoutCore/Types/AlignmentID.swift new file mode 100644 index 0000000..b1dfa18 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/AlignmentID.swift @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +/** + AlignmentID can be used to define a custom alignment behavior. + */ +public protocol AlignmentID: Sendable { + static func defaultValue(in context: ElementDimensions) -> CGFloat +} + +public struct AnyAlignmentID: Hashable, Sendable { + private let alignmentID: AlignmentID.Type + internal let axis: Axis + + init(_ alignmentID: AlignmentID.Type, axis: Axis) { + self.alignmentID = alignmentID + self.axis = axis + } + + func defaultValue(in context: ElementDimensions) -> CGFloat { + alignmentID.defaultValue(in: context) + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(alignmentID)) + } + + public static func == (lhs: AnyAlignmentID, rhs: AnyAlignmentID) -> Bool { + return ObjectIdentifier(lhs.alignmentID) == ObjectIdentifier(rhs.alignmentID) + } +} + +// MARK: - Vertical Alignment IDs + +private struct TopAlignment: AlignmentID { + static func defaultValue(in context: ElementDimensions) -> CGFloat { + 0 + } +} + +private struct VerticalCenterAlignment: AlignmentID { + static func defaultValue(in context: ElementDimensions) -> CGFloat { + context.height / 2 + } +} + +private struct BottomAlignment: AlignmentID { + static func defaultValue(in context: ElementDimensions) -> CGFloat { + context.height + } +} + +public extension VerticalAlignment { + static let top = VerticalAlignment(TopAlignment.self) + static let center = VerticalAlignment(VerticalCenterAlignment.self) + static let bottom = VerticalAlignment(BottomAlignment.self) +} + +// MARK: - Horizontal Alignment IDs + +private struct LeadingAlignment: AlignmentID { + static func defaultValue(in context: ElementDimensions) -> CGFloat { + LayoutContext.layoutDirection == .rightToLeft ? context.width : 0 + } +} + +private struct HorizontalCenterAlignment: AlignmentID { + static func defaultValue(in context: ElementDimensions) -> CGFloat { + context.width / 2 + } +} + +private struct TrailingAlignment: AlignmentID { + static func defaultValue(in context: ElementDimensions) -> CGFloat { + LayoutContext.layoutDirection == .rightToLeft ? 0 : context.width + } +} + +public extension HorizontalAlignment { + static let leading = HorizontalAlignment(LeadingAlignment.self) + static let center = HorizontalAlignment(HorizontalCenterAlignment.self) + static let trailing = HorizontalAlignment(TrailingAlignment.self) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/AlignmentTypes.swift b/Sources/QuickLayout/QuickLayoutCore/Types/AlignmentTypes.swift new file mode 100644 index 0000000..051715d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/AlignmentTypes.swift @@ -0,0 +1,57 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +/** + Specifies alignment behavior along an element's horizontal axis. + Horizontal alignment can be created with a custom AlignmentID + for full control over alignment behavior. + */ +public struct HorizontalAlignment: Sendable { + public let alignmentID: AnyAlignmentID + public init(_ alignmentID: AlignmentID.Type) { + self.alignmentID = AnyAlignmentID(alignmentID, axis: .horizontal) + } +} + +/** + Specifies alignment behavior along an element's vertical axis. + Vertical alignment can be created with a custom AlignmentID + for full control over alignment behavior. + */ +public struct VerticalAlignment: Sendable { + public let alignmentID: AnyAlignmentID + public init(_ alignmentID: AlignmentID.Type) { + self.alignmentID = AnyAlignmentID(alignmentID, axis: .vertical) + } +} + +/** + Specifies alignment behavior in both horizontal and vertical directions. + Pass in any combination of horizontal and vertical alignment to achieve + the desired 2D alignment. + */ +public struct Alignment: Sendable { + let horizontal: HorizontalAlignment + let vertical: VerticalAlignment + + public init(horizontal: HorizontalAlignment, vertical: VerticalAlignment) { + self.horizontal = horizontal + self.vertical = vertical + } + + public static let topLeading = Alignment(horizontal: .leading, vertical: .top) + public static let top = Alignment(horizontal: .center, vertical: .top) + public static let topTrailing = Alignment(horizontal: .trailing, vertical: .top) + public static let leading = Alignment(horizontal: .leading, vertical: .center) + public static let center = Alignment(horizontal: .center, vertical: .center) + public static let trailing = Alignment(horizontal: .trailing, vertical: .center) + public static let bottomLeading = Alignment(horizontal: .leading, vertical: .bottom) + public static let bottom = Alignment(horizontal: .center, vertical: .bottom) + public static let bottomTrailing = Alignment(horizontal: .trailing, vertical: .bottom) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/Axis.swift b/Sources/QuickLayout/QuickLayoutCore/Types/Axis.swift new file mode 100644 index 0000000..80654bc --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/Axis.swift @@ -0,0 +1,15 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +@objc(QLAxis) +@frozen +public enum Axis: Int8, Sendable { + case horizontal + case vertical +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/AxisSet.swift b/Sources/QuickLayout/QuickLayoutCore/Types/AxisSet.swift new file mode 100644 index 0000000..e641d65 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/AxisSet.swift @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +public struct AxisSet: OptionSet, Sendable { + public let rawValue: Int8 + + public init(rawValue: Int8) { + self.rawValue = rawValue + } + + public static let horizontal = AxisSet(rawValue: 1 << 0) + public static let vertical = AxisSet(rawValue: 1 << 1) +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/Edge.swift b/Sources/QuickLayout/QuickLayoutCore/Types/Edge.swift new file mode 100644 index 0000000..45261f0 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/Edge.swift @@ -0,0 +1,16 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +@frozen +public enum Edge: Int8, Sendable { + case top + case bottom + case leading + case trailing +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/EdgeInsets.swift b/Sources/QuickLayout/QuickLayoutCore/Types/EdgeInsets.swift new file mode 100644 index 0000000..ec14f72 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/EdgeInsets.swift @@ -0,0 +1,32 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +public struct EdgeInsets: Sendable { + + public var top: CGFloat + public var bottom: CGFloat + public var leading: CGFloat + public var trailing: CGFloat + + public static let zero = EdgeInsets() + + public init() { + self.top = 0 + self.leading = 0 + self.bottom = 0 + self.trailing = 0 + } + + public init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { + self.top = top + self.leading = leading + self.bottom = bottom + self.trailing = trailing + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/EdgeSet.swift b/Sources/QuickLayout/QuickLayoutCore/Types/EdgeSet.swift new file mode 100644 index 0000000..ced7432 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/EdgeSet.swift @@ -0,0 +1,25 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +public struct EdgeSet: OptionSet, Sendable { + + public let rawValue: Int8 + + public init(rawValue: Int8) { + self.rawValue = rawValue + } + + public static let all: EdgeSet = [.top, .bottom, .leading, .trailing] + public static let top = EdgeSet(rawValue: 1 << Edge.top.rawValue) + public static let bottom = EdgeSet(rawValue: 1 << Edge.bottom.rawValue) + public static let leading = EdgeSet(rawValue: 1 << Edge.leading.rawValue) + public static let trailing = EdgeSet(rawValue: 1 << Edge.trailing.rawValue) + public static let horizontal: EdgeSet = [.leading, .trailing] + public static let vertical: EdgeSet = [.top, .bottom] +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/ElementDimensions.swift b/Sources/QuickLayout/QuickLayoutCore/Types/ElementDimensions.swift new file mode 100644 index 0000000..e5edea0 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/ElementDimensions.swift @@ -0,0 +1,61 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +/** + ElementDimensions is used to store dimensional information, + including sizing and alignment info. + */ +public struct ElementDimensions: Sendable { + public let width: CGFloat + public let height: CGFloat + internal let alignmentGuides: AlignmentGuides + + // MARK: - Public API + + public init(_ size: CGSize) { + self.init(size, alignmentGuides: AlignmentGuidesResolver.none()) + } + + public subscript(_ horizontalAlignment: HorizontalAlignment) -> CGFloat { + self[horizontalAlignment.alignmentID] + } + + public subscript(_ verticalAlignment: VerticalAlignment) -> CGFloat { + self[verticalAlignment.alignmentID] + } + + // MARK: - Internal API + + internal init(_ size: CGSize, alignmentGuides: AlignmentGuides) { + self.width = size.width + self.height = size.height + self.alignmentGuides = alignmentGuides + } + + internal subscript(_ alignmentID: AnyAlignmentID) -> CGFloat { + let resolvedValue = self[explicit: alignmentID] ?? alignmentID.defaultValue(in: self) + let sanitizedValue = (resolvedValue.isInfinite || resolvedValue.isNaN) ? 0 : resolvedValue + return sanitizedValue + } + + // MARK: - Private + + private subscript(explicit alignmentID: AnyAlignmentID) -> CGFloat? { + guard let alignmentGuide = alignmentGuides[alignmentID] else { + return nil + } + // If the alignment guide requests the value it is aligned against, + // return the default value instead of recursing. + let dimensions = ElementDimensions( + CGSize(width: width, height: height), + alignmentGuides: alignmentGuides.disregarding(alignmentID) + ) + return alignmentGuide(dimensions) + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/Flexibility.swift b/Sources/QuickLayout/QuickLayoutCore/Types/Flexibility.swift new file mode 100644 index 0000000..b995d9a --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/Flexibility.swift @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@objc(QLFlexibility) +public enum Flexibility: Int8, Sendable { + case fixedSize + case partial + case fullyFlexible +} + +@inlinable +public func max(_ a: Flexibility, _ b: Flexibility) -> Flexibility { + a.rawValue > b.rawValue ? a : b +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/LayoutDirection.swift b/Sources/QuickLayout/QuickLayoutCore/Types/LayoutDirection.swift new file mode 100644 index 0000000..bd195f0 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/LayoutDirection.swift @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +@objc +public enum LayoutDirection: Int8, Sendable { + case leftToRight + case rightToLeft +} + +struct DefaultLayoutDirection { + static let value: LayoutDirection = NSParagraphStyle.defaultWritingDirection(forLanguage: nil) == .rightToLeft ? .rightToLeft : .leftToRight +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/LayoutNode.swift b/Sources/QuickLayout/QuickLayoutCore/Types/LayoutNode.swift new file mode 100644 index 0000000..6ae811d --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/LayoutNode.swift @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation +import UIKit + +public struct GridInfo: Sendable { + public let alignment: Alignment? + public let unitPoint: UnitPoint? + public let columnAlignment: HorizontalAlignment? +} + +public struct LayoutNode: Sendable { + + public static let empty = LayoutNode(view: nil, dimensions: ElementDimensions(.zero)) + + public let view: UIView? + public let dimensions: ElementDimensions + public let gridInfo: GridInfo? + public let children: [Child] + + public init(view: UIView?, dimensions: ElementDimensions, gridInfo: GridInfo? = nil) { + self.view = view + self.dimensions = dimensions + self.gridInfo = gridInfo + self.children = [] + } + + init(view: UIView?, size: CGSize, children: [Child], alignmentGuides: AlignmentGuides) { + self.view = view + self.children = children + self.gridInfo = nil + self.dimensions = ElementDimensions(size, alignmentGuides: alignmentGuides) + } + init(view: UIView?, dimensions: ElementDimensions, gridInfo: GridInfo?, children: [Child]) { + self.view = view + self.dimensions = dimensions + self.gridInfo = gridInfo + self.children = children + } + + public var size: CGSize { + return CGSize(width: dimensions.width, height: dimensions.height) + } + + public struct Child: Sendable { + + public let position: CGPoint + public let layout: LayoutNode + + public init(position: CGPoint, layout: LayoutNode) { + self.position = position + self.layout = layout + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutCore/Types/UnitPoint.swift b/Sources/QuickLayout/QuickLayoutCore/Types/UnitPoint.swift new file mode 100644 index 0000000..901cc13 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutCore/Types/UnitPoint.swift @@ -0,0 +1,20 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Foundation + +public struct UnitPoint: Sendable { + + public let x: CGFloat + public let y: CGFloat + + public init(x: CGFloat, y: CGFloat) { + self.x = x + self.y = y + } + +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayout.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayout.swift new file mode 100644 index 0000000..4eb4897 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayout.swift @@ -0,0 +1,85 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SwiftSyntax +import SwiftSyntaxMacros + +public struct QuickLayout: ExtensionMacro, MemberMacro, MemberAttributeMacro { + // MARK: - ExtensionMacro + + public static func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingExtensionsOf type: some TypeSyntaxProtocol, + conformingTo protocols: [TypeSyntax], + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { + let declSyntax: DeclSyntax = + """ + extension \(type.trimmed): HasBody {} + """ + guard let extensionSyntax = declSyntax.as(ExtensionDeclSyntax.self) else { + return [] + } + return [extensionSyntax] + } + + // MARK: - MemberMacro + + public static func expansion( + of node: SwiftSyntax.AttributeSyntax, + providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax, + in context: some SwiftSyntaxMacros.MacroExpansionContext + ) throws -> [SwiftSyntax.DeclSyntax] { + let memberFunctions = declaration.memberFunctions.map { $0.identity } + var memberSyntax: String = "" + for macroFunction in QuickLayoutMacroFunction.allCases { + guard !memberFunctions.contains(macroFunction.identity) else { + continue + } + memberSyntax += macroFunction.standaloneImplementation + } + return [DeclSyntax(stringLiteral: memberSyntax)] + } + + // MARK: - MemberAttributeMacro + + public static func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AttributeSyntax] { + if let memberFunction = member.function { + for macroFunction in QuickLayoutMacroFunction.allCases { + guard macroFunction.identity == memberFunction.identity else { + continue + } + switch macroFunction.collisionBehavior { + case .deferToProvidedDefinition: + continue + case .injectIntoProvidedDefinition(let impl): + return ["@_QuickLayoutInjection(\"\(raw: impl(memberFunction))\")"] + } + } + } else if let memberVariable = member.variable { + for macroVariable in QuickLayoutMacroVariable.allCases { + guard macroVariable.identity == memberVariable.identity else { + continue + } + switch macroVariable.memberBehavior { + case .prependLayoutBuilderAttribute: + guard !memberVariable.modifierNames.contains("LayoutBuilder") else { + continue + } + return ["@LayoutBuilder"] + } + } + } + return [] + } +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutFunctionIdentity.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutFunctionIdentity.swift new file mode 100644 index 0000000..d7b960c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutFunctionIdentity.swift @@ -0,0 +1,31 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +struct QuickLayoutFunctionIdentity: Equatable { + enum Scope: Equatable { + case instance, type + } + + struct Parameter: Equatable { + let externalName: String? + let type: String + } + + let scope: Scope + let name: String + let parameters: [Parameter] + let returnType: String + + init(scope: Scope, name: String, parameters: [(externalName: String?, type: String)], returnType: String) { + self.scope = scope + self.name = name + self.returnType = normalize(type: returnType) + self.parameters = parameters.map { + Parameter(externalName: $0.externalName, type: normalize(type: $0.type)) + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutInjection.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutInjection.swift new file mode 100644 index 0000000..12dd85c --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutInjection.swift @@ -0,0 +1,67 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SwiftSyntax +import SwiftSyntaxMacros + +enum QuickLayoutInjectionError: Error { + case failedToParseInjectionArgument + case unsupportedExpressionStringInput + + var localizedDescription: String { + switch self { + case .failedToParseInjectionArgument: + return "Failed to parse injection argument" + case .unsupportedExpressionStringInput: + return "Interpolated strings with expression input are not supported" + } + } +} + +public struct QuickLayoutInjection: BodyMacro { + static public func expansion( + of node: AttributeSyntax, + providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + // Extract the argument from @_QuickLayoutInjection() + guard let argument = node.arguments?.as(LabeledExprListSyntax.self)?.first?.expression.as(StringLiteralExprSyntax.self) else { + throw QuickLayoutInjectionError.failedToParseInjectionArgument + } + // Map this to a string value + let argumentValue = try argument.segments.compactMap { segment -> String? in + switch segment { + case .stringSegment(let content): + return content.content.text + case .expressionSegment: + throw QuickLayoutInjectionError.unsupportedExpressionStringInput + } + }.joined() + let injectedSyntax = CodeBlockItemSyntax(stringLiteral: "\(argumentValue)") + guard let statements = declaration.body?.statements, let function = declaration.function else { + return [injectedSyntax] + } + var modifiedStatements = [CodeBlockItemSyntax]() + var hasPerformedInjection = false + // Step through each statement, injecting the argument directly after the + // call to super.. By performing the injection at this + // point, it becomes similar in behavior to an invisible superclass. Functions + // with injection applied can do work before or after the injection, and in + // general the expectations line up with what engineers might expect. + for statement in statements { + modifiedStatements.append(statement) + if !hasPerformedInjection && statement.description.trimmingWhitespaceAndNewlines == function.defaultSuperCall { + modifiedStatements.append(injectedSyntax) + hasPerformedInjection = true + } + } + if !hasPerformedInjection { + modifiedStatements.append(injectedSyntax) + } + return modifiedStatements + } +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroFunction.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroFunction.swift new file mode 100644 index 0000000..1483d81 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroFunction.swift @@ -0,0 +1,100 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SwiftSyntax + +/** + Defines the functions that the @QuickLayout macro interacts with. +*/ +enum QuickLayoutMacroFunction: CaseIterable { + + /** + Defines how functions handle collisions with existing implementations. + */ + enum CollisionBehavior { + case injectIntoProvidedDefinition(_ impl: (FunctionDeclSyntax) -> String) + case deferToProvidedDefinition + } + + case willMoveToWindow + case layoutSubviews + case sizeThatFits + case quick_flexibility +} + +/** + Defines behavior and attributes for each QuickLayoutMacroFunction case. +*/ +extension QuickLayoutMacroFunction { + var identity: QuickLayoutFunctionIdentity { + switch self { + case .willMoveToWindow: + .init(scope: .instance, name: "willMove", parameters: [(externalName: "toWindow", type: "UIWindow?")], returnType: "Void") + case .layoutSubviews: + .init(scope: .instance, name: "layoutSubviews", parameters: [], returnType: "Void") + case .sizeThatFits: + .init(scope: .instance, name: "sizeThatFits", parameters: [(externalName: nil, type: "CGSize")], returnType: "CGSize") + case .quick_flexibility: + .init(scope: .instance, name: "quick_flexibility", parameters: [(externalName: "for", type: "Axis")], returnType: "Flexibility") + } + } + + var standaloneImplementation: String { + switch self { + case .willMoveToWindow: + """ + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + """ + case .layoutSubviews: + """ + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + """ + case .sizeThatFits: + """ + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + """ + case .quick_flexibility: + """ + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + + """ + } + } + + var collisionBehavior: CollisionBehavior { + switch self { + case .willMoveToWindow: + .injectIntoProvidedDefinition { functionSyntax in + let windowParameterName = functionSyntax.parameterName(at: 0) + return "_QuickLayoutViewImplementation.willMove(self, toWindow: \(windowParameterName))" + } + case .layoutSubviews: + .injectIntoProvidedDefinition { _ in + return "_QuickLayoutViewImplementation.layoutSubviews(self)" + } + case .sizeThatFits: .deferToProvidedDefinition + case .quick_flexibility: .deferToProvidedDefinition + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroHelpers.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroHelpers.swift new file mode 100644 index 0000000..1c7b3be --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroHelpers.swift @@ -0,0 +1,122 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SwiftSyntax +import SwiftSyntaxMacros + +extension DeclGroupSyntax { + var memberFunctions: [FunctionDeclSyntax] { + return memberBlock.members.compactMap { + $0.decl.function + } + } +} + +extension DeclSyntaxProtocol { + var function: FunctionDeclSyntax? { + return self.as(FunctionDeclSyntax.self) + } + + var variable: VariableDeclSyntax? { + return self.as(VariableDeclSyntax.self) + } +} + +extension FunctionDeclSyntax { + func parameterName(at index: Int) -> String { + let paramaterSyntax = signature.parameterClause.parameters.map { $0 }[index] + return paramaterSyntax.internalName.text + } + + var identity: QuickLayoutFunctionIdentity { + let isScopedToType = modifiers.contains { + $0.name.text == "static" || $0.name.text == "class" + } + let parameters = signature.parameterClause.parameters.map { + (externalName: $0.externalName.text == "_" ? nil : $0.externalName.text, type: $0.type.description) + } + let returnType = signature.returnClause?.type.description ?? "Void" + let scope: QuickLayoutFunctionIdentity.Scope = isScopedToType ? .type : .instance + return QuickLayoutFunctionIdentity(scope: scope, name: name.text, parameters: parameters, returnType: returnType) + } + + var defaultSuperCall: String { + var parameterSyntax = "" + for (index, parameter) in signature.parameterClause.parameters.enumerated() { + if parameter.firstName.text == "_" { + parameterSyntax += parameter.internalName.text + } else { + parameterSyntax += "\(parameter.firstName.text): \(parameter.internalName.text)" + } + if index != signature.parameterClause.parameters.count - 1 { + parameterSyntax += ", " + } + } + return "super.\(name)(\(parameterSyntax))" + } +} + +extension VariableDeclSyntax { + var identity: QuickLayoutVariableIdentity { + let isScopedToType = modifiers.contains { + $0.name.text == "static" || $0.name.text == "class" + } + let returnType = returnTypeAnnotation?.type.description ?? "Void" + let scope: QuickLayoutVariableIdentity.Scope = isScopedToType ? .type : .instance + return QuickLayoutVariableIdentity(scope: scope, name: name?.text ?? "none", returnType: returnType) + } + + var returnTypeAnnotation: TypeAnnotationSyntax? { + bindings.first?.typeAnnotation + } + + var name: TokenSyntax? { + bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier + } + + var modifierNames: [String] { + attributes.compactMap { + guard let attribute = $0.as(AttributeSyntax.self) else { return nil } + guard let value = attribute.attributeName.as(IdentifierTypeSyntax.self) else { return nil } + return value.name.text + } + } +} + +extension FunctionParameterSyntax { + var internalName: TokenSyntax { + return secondName ?? firstName + } + + var externalName: TokenSyntax { + return firstName + } +} + +extension String { + var trimmingWhitespaceAndNewlines: String { + drop(while: { $0.isNewline || $0.isWhitespace }) + .reversed() + .drop(while: { $0.isNewline || $0.isWhitespace }) + .reversed() + .map { String($0) } + .joined() + } +} + +func normalize(type: String) -> String { + let characters = type.trimmingWhitespaceAndNewlines.map { + String($0) + } + return characters.reduce(into: "") { aggregate, character in + if character == "." || character == " " { + aggregate = "" + } else { + aggregate.append(character) + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroVariable.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroVariable.swift new file mode 100644 index 0000000..615f1db --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutMacroVariable.swift @@ -0,0 +1,42 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SwiftSyntax + +/** + Defines the variables that the @QuickLayout macro interacts with. +*/ +enum QuickLayoutMacroVariable: CaseIterable { + + /** + Defines how variables are handled. + */ + enum MemberBehavior { + case prependLayoutBuilderAttribute + } + + case body +} + +/** + Defines behavior and attributes for each QuickLayoutMacroVariable case. +*/ +extension QuickLayoutMacroVariable { + var identity: QuickLayoutVariableIdentity { + switch self { + case .body: + .init(scope: .instance, name: "body", returnType: "Layout") + } + } + + var memberBehavior: MemberBehavior { + switch self { + case .body: + .prependLayoutBuilderAttribute + } + } +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutPlugin.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutPlugin.swift new file mode 100644 index 0000000..3382c51 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutPlugin.swift @@ -0,0 +1,17 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxMacros + +@main +struct QuickLayoutPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + QuickLayout.self, QuickLayoutInjection.self, + ] +} diff --git a/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutVariableIdentity.swift b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutVariableIdentity.swift new file mode 100644 index 0000000..9f0e230 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacro/QuickLayoutVariableIdentity.swift @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +struct QuickLayoutVariableIdentity: Equatable { + enum Scope: Equatable { + case instance, type + } + + let scope: Scope + let name: String + let returnType: String + + init(scope: Scope, name: String, returnType: String) { + self.scope = scope + self.name = name + self.returnType = normalize(type: returnType) + } +} diff --git a/Sources/QuickLayout/QuickLayoutMacroTests/QuickLayoutTests.swift b/Sources/QuickLayout/QuickLayoutMacroTests/QuickLayoutTests.swift new file mode 100644 index 0000000..d090901 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutMacroTests/QuickLayoutTests.swift @@ -0,0 +1,478 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import QuickLayoutMacro +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +// patternlint-disable meta-subclass-view + +let testMacros: [String: Macro.Type] = [ + "QuickLayout": QuickLayout.self, + "_QuickLayoutInjection": QuickLayoutInjection.self, +] + +class QuickLayoutTests: XCTestCase { + func testBasicMacroExpansion() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: Layout { + EmptyLayout() + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testApplicableMethodsDeferToProvidedImplementation() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: Layout { + EmptyLayout() + } + + public func sizeThatFits(_ size: CGSize) -> CGSize { + return uniqueSizeValue + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return uniqueFlexibilityValue + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + public func sizeThatFits(_ size: CGSize) -> CGSize { + return uniqueSizeValue + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return uniqueFlexibilityValue + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testApplicableMethodsInjectQuickLayoutBridgeementation() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: Layout { + EmptyLayout() + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + someUniqueWillMoveToWindowLogic() + } + + public override func layoutSubviews() { + super.layoutSubviews() + someUniqueLayoutSubviewsLogic() + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + someUniqueWillMoveToWindowLogic() + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + someUniqueLayoutSubviewsLogic() + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testTypeScopedFunctionsDoNotConflict() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: Layout { + EmptyLayout() + } + + static func sizeThatFits(_ size: CGSize) -> CGSize { + return uniqueSizeValue + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + static func sizeThatFits(_ size: CGSize) -> CGSize { + return uniqueSizeValue + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testOverloadedFunctionsDoNotConflict() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: Layout { + EmptyLayout() + } + + func sizeThatFits(_ size: CGSize) -> NotACGSize { + return uniqueSizeValue + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + func sizeThatFits(_ size: CGSize) -> NotACGSize { + return uniqueSizeValue + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testParameterMismatchedFunctionsDoNotConflict() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: Layout { + EmptyLayout() + } + + func sizeThatFits(_ notASize: NotACGSize) -> CGSize { + return uniqueSizeValue + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + func sizeThatFits(_ notASize: NotACGSize) -> CGSize { + return uniqueSizeValue + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testLayoutBuilderAttributeNotAddedIfPresent() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testLayoutBuilderAttributeOnlyAddedToBody() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: Layout { + EmptyLayout() + } + + var notBody: Layout { + EmptyLayout() + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: Layout { + EmptyLayout() + } + + var notBody: Layout { + EmptyLayout() + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } + + func testLayoutBuilderAttributeAppliesToAnyLayout() throws { + assertMacroExpansion( + #""" + @QuickLayout + class TestView: UIView { + var body: any Layout { + EmptyLayout() + } + } + """#, + expandedSource: + #""" + class TestView: UIView { + @LayoutBuilder + var body: any Layout { + EmptyLayout() + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + _QuickLayoutViewImplementation.willMove(self, toWindow: newWindow) + } + + public override func layoutSubviews() { + super.layoutSubviews() + _QuickLayoutViewImplementation.layoutSubviews(self) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size) + } + + public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility { + return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis) + } + } + + extension TestView: HasBody { + } + """#, + macros: testMacros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/1_handshake.imageset/1_handshake.jpg b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/1_handshake.imageset/1_handshake.jpg new file mode 100644 index 0000000..5b4fda2 Binary files /dev/null and b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/1_handshake.imageset/1_handshake.jpg differ diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/1_handshake.imageset/Contents.json b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/1_handshake.imageset/Contents.json new file mode 100644 index 0000000..6644847 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/1_handshake.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "1_handshake.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/2_superbueno.imageset/2_superbueno.jpg b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/2_superbueno.imageset/2_superbueno.jpg new file mode 100644 index 0000000..980c566 Binary files /dev/null and b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/2_superbueno.imageset/2_superbueno.jpg differ diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/2_superbueno.imageset/Contents.json b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/2_superbueno.imageset/Contents.json new file mode 100644 index 0000000..8cc3af5 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/2_superbueno.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "2_superbueno.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/3_overstory.imageset/3_overstory.jpg b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/3_overstory.imageset/3_overstory.jpg new file mode 100644 index 0000000..ca430be Binary files /dev/null and b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/3_overstory.imageset/3_overstory.jpg differ diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/3_overstory.imageset/Contents.json b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/3_overstory.imageset/Contents.json new file mode 100644 index 0000000..893b323 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/3_overstory.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "3_overstory.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/4_martiny.imageset/4_martiny.jpg b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/4_martiny.imageset/4_martiny.jpg new file mode 100644 index 0000000..fff8bfd Binary files /dev/null and b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/4_martiny.imageset/4_martiny.jpg differ diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/4_martiny.imageset/Contents.json b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/4_martiny.imageset/Contents.json new file mode 100644 index 0000000..ad7b1a0 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/4_martiny.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "4_martiny.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/Contents.json b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sources/QuickLayout/QuickLayoutShowcaseAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/QuickLayout/docs/.gitignore b/Sources/QuickLayout/docs/.gitignore new file mode 100644 index 0000000..b2d6de3 --- /dev/null +++ b/Sources/QuickLayout/docs/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/Sources/QuickLayout/docs/.npmrc b/Sources/QuickLayout/docs/.npmrc new file mode 100644 index 0000000..a16e3e1 --- /dev/null +++ b/Sources/QuickLayout/docs/.npmrc @@ -0,0 +1,2 @@ +# Stop people use npm instead of yarn by accident +engine-strict = true diff --git a/Sources/QuickLayout/docs/README.mdx b/Sources/QuickLayout/docs/README.mdx new file mode 100644 index 0000000..07f432d --- /dev/null +++ b/Sources/QuickLayout/docs/README.mdx @@ -0,0 +1,25 @@ +# Website + +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +### Installation + +``` +$ yarn +``` + +### Local Development + +``` +$ yarn start +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### Build + +``` +$ yarn build +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. diff --git a/Sources/QuickLayout/docs/babel.config.js b/Sources/QuickLayout/docs/babel.config.js new file mode 100644 index 0000000..fb9cc50 --- /dev/null +++ b/Sources/QuickLayout/docs/babel.config.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/Sources/QuickLayout/docs/docs/code-samples.mdx b/Sources/QuickLayout/docs/docs/code-samples.mdx new file mode 100644 index 0000000..b1dee43 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/code-samples.mdx @@ -0,0 +1,156 @@ +--- +id: code-samples +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Code Samples + +Here is few more samples to get you started. + + +

+ +The screen above was created with the following layout: +```swift +import QuickLayout + +@QuickLayout +class LoginView: UIView { + + var keyboardShown: Bool = false { + didSet { + createAccountButton.isHidden = keyboardShown + setNeedsLayout() + } + } + + let appIconView = IconView() + let usernameField = TextField() + let passwordField = PasswordField() + let logInButton = PillButton(title: "Log in") + let forgotPasswordButton = TextButton(title: "Forgot password?") + let createAccountButton = PillButton(title: "Create new account") + + var body: Layout { + VStack { + Spacer() + appIconView + Spacer() + Spacer(8) + usernameField + Spacer(8) + passwordField + Spacer(8) + logInButton + Spacer(8) + forgotPasswordButton + Spacer(minLength: 8) + if !createAccountButton.isHidden { + createAccountButton + } + }.padding(.horizontal: 16) + } +} +``` +---- + + +

+ +```swift +import QuickLayout + +final class PlayerViewController: UIViewController { + + private var albumArt: UIImageView + private var songTitle: UILabel + private var artistName: UILabel + private var currentTime: UILabel + private var totalTime: UILabel + private var progressBar: UIView + private var progressTrack: UIView + private var previousButton: UIButton + private var playButton: UIButton + private var nextButton: UIButton + + @LayoutBuilder + private func layout() -> Layout { + VStack { + Spacer() + albumArt + .resizable() + .aspectRatio(CGSize(width: 1, height: 1)) + .padding(.horizontal, 40) + Spacer(24) + songTitle + .padding(.horizontal, 20) + Spacer(8) + artistName + .padding(.horizontal, 20) + Spacer(32) + + VStack(spacing: 8) { + ZStack(alignment: .leading) { + progressTrack + .resizable() + .frame(height: 4) + progressBar + .resizable() + .frame(width: 100, height: 4) + .layoutPriority(1) + } + .padding(.horizontal, 20) + HStack { + currentTime + Spacer() + totalTime + } + .padding(.horizontal, 20) + } + + Spacer(32) + + HStack(spacing: 32) { + previousButton + .resizable() + .frame(width: 44, height: 44) + playButton + .resizable() + .frame(width: 60, height: 60) + nextButton + .resizable() + .frame(width: 44, height: 44) + } + + Spacer(minLength: 24) + } + } + + // @QuickLayout is not yet supported for view controllers, so + // manual layout integration is needed (we add subviews in `viewDidLoad`, + // and apply frames in `viewDidLayoutSubviews`). + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .white + self.view.addSubviews { + albumArt + songTitle + artistName + currentTime + totalTime + progressBar + progressTrack + previousButton + playButton + nextButton + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + let layoutRect = view.bounds.inset(by: view.safeAreaInsets) + layout().applyFrame(layoutRect) + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/concepts/double-measurements.mdx b/Sources/QuickLayout/docs/docs/concepts/double-measurements.mdx new file mode 100644 index 0000000..03a8f82 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/concepts/double-measurements.mdx @@ -0,0 +1,101 @@ +--- +id: double-measurement +--- + +# Double Measurement + +Stacks in QuickLayout introduce an additional concept called [flexibility](flexibilities.mdx) to optimize layouts. Each standard view in UIKit was annotated with flexibility to improve how they are measured and laid out. However, custom UIView subclasses default to “partial flexibility,” which can lead to inefficient layouts and multiple measurement passes. In this article, we’ll delve into how these extra measurements occur, how they can accumulate, and what APIs QuickLayout provides to optimize layouts. + +### The Double Measurement Problem +When a stack lays out its subviews, each view needs to be measured. In some systems — SwiftUI in particular — views may be measured multiple times with different proposed size before the final layout is determined. This process can be costly in complex interfaces, causing stuttering or dropped frames. + +QuickLayout aims to avoid extra measurements whenever possible by leveraging flexibility annotations. Standard UIKit elements are annotated to communicate to the layout system how “rigid” or “flexible” they are, helping QuickLayout decide the most efficient measurement order. If multiple views in a Stack are partially flexible, however, more than one measurement per view can still occur. + +### Comparing Imperative Layout +To understand why repeated measurement is a problem, let’s compare it to an imperative layout approach. +Consider the following layout: + +```swift +HStack { + Text("Lorem ipsum ...") + Image("someImage") +} +``` + +### Manual Layout Example +In an imperative style (pure UIKit), a developer might write the following: +```swift +// Measure the image view once +let imageViewSize = imageView.sizeThatFits(availableSize) +imageView.frame = CGRect(x: X, y: Y, width: imageViewSize.width, height: imageViewSize.height) + +// Measure the label once, taking into account image width + padding +let labelAvailableWidth = availableSize.width - imageViewSize.width - padding +let labelSize = label.sizeThatFits(CGSize(width: labelAvailableWidth, height: availableSize.height)) +label.frame = CGRect(x: X, y: Y, width: labelSize.width, height: labelSize.height) +``` + +Here, each view is measured exactly once. The developer manually controls the order of measurement, so there’s no wasted computation. It’s straightforward and efficient but requires more boilerplate code. + +### Triple Measurement in SwiftUI Stacks +In SwiftUI, a similar layout might look like: +```swift +HStack { + Text("Lorem ipsum ...") + Image("someImage") +} +``` + +Under the hood, SwiftUI will measure each subview three times with varying proposed sizes (zero width, 'infinite' width and then final proposed width). This approach allows the library to infer the best distribution of space, but it leads to more repeated measurements—especially when there are many text elements or complex views. + +Now consider a more involved layout: +```swift +HStack { + VStack { + Text("Lorem ipsum ...") + Text("Lorem ipsum ...") + Text("Lorem ipsum ...") + } + VStack { + Image("someImage") + Image("someImage") + Image("someImage") + } +} +``` + +SwiftUI will pre-measure each Text view twice before making the final, third, measurement. Such an algorithm essentially multiplies the performance cost by the number of text elements. In scrolling lists, this can lead to visible jank or dropped frames, affecting the user experience and potentially harming business metrics. + +### QuickLayout Stacks +To combat this, QuickLayout uses [flexibility](flexibilities.mdx) annotations on standard UIKit components: +- Fixed flexibility: for views whose size is effectively constant (e.g., UIImageView with a fixed image size). +- Full flexibility: for views that can take all remaining space (e.g., UIScrollView, UICollectionView). +- Partial flexibility (default for UILabels and custom UIView subclasses): Indicates that the system might need to measure the view more than once to figure out its optimal size. + +By identifying certain views as fixed, QuickLayout can measure them first and subtract their space from the total, thus reducing redundant measurements for the partially flexible views. However, if a stack contains multiple “partially flexible” views—such as custom views or UILabel instances—double measurements can still occur. + +### How to Avoid Double Measurements +1. **Annotate Infrastructure Views with Flexibility.** If you own or create infrastructure-level views, mark them as either fixed or fully flexible to ensure QuickLayout can measure them in one pass. This is especially important for heavily used views (e.g., a shared custom button, labels, etc). (see example diff D70773705) + +2. **Use Layout Priorities.** When you can’t or don’t want to annotate certain views with fixed/full flexibility, you can guide QuickLayout’s measurement order using [layoutPriority(_:)](layout/layout-priority.mdx). Views with higher layout priority are measured first, which helps reduce multiple measurements for lower-priority siblings. For an example, see D70774081. + +For example, to reduce double measurements in the labels below, I give the VStack containing buttons a higher priority: +```swift +HStack { + VStack { + label1 + label2 + label2 + } + VStack { + aSmallCustomButton1 + aSmallCustomButton2 + aSmallCustomButton3 + } + /// Raising the layout priority to avoid double measuring + /// the first VStack with labels. + .layoutPriority(1) +} +``` + +By assigning a higher layout priority to the image stack, QuickLayout measures buttons first, subtracts their size from the available space, and then measures the stack with text. This avoids repeated measurements of the text views. diff --git a/Sources/QuickLayout/docs/docs/concepts/flexibilities.mdx b/Sources/QuickLayout/docs/docs/concepts/flexibilities.mdx new file mode 100644 index 0000000..393d339 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/concepts/flexibilities.mdx @@ -0,0 +1,26 @@ +--- +id: flexibilities +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Flexibility + +Flexibility describes how a view responds to the size that is proposed by its parent during layout. There are three primary categories of flexibility: inflexible, partially flexible, and fully flexible. + +### Inflexible Views +Inflexible views have a fixed size, meaning they disregard the proposed size and always return their intrinsic dimensions. These views are designed to maintain a constant size regardless of the surrounding layout. For example, a UISwitch always returns a fixed size of 51x31 points, and a UIImageView returns the size of the image it contains, regardless of how much space is available. Stacks measure inflexible views first to reduce double measuremnts of text. + +### Partially Flexible Views +Partially flexible views determine their size based on a combination of their content and the proposed size. They can adjust within certain bounds, depending on how much space is available, but their final size is still influenced by their content. A UILabel is a common example of a partially flexible view. It adjusts its width and height based on the text it contains, but it also takes the proposed size into account. + +### Fully Flexible Views +Fully flexible views do not have an intrinsic size and will adopt the proposed size without conditions. These views simply expand or contract to match the size their parent suggests. Examples of fully flexible views include a plain UIView without a implemented sizeThatFits, a UIScrollView, or a UITextView. These views can grow to fill any available space. + +### Axis-Specific Flexibility + +Some views can have different types of flexibility depending on the axis. For example, a UITextField is fully flexible horizontally, allowing it to stretch or shrink to fit within its container, but it is inflexible vertically, maintaining a consistent height regardless of the proposed size. + +### Layout Elements + +Layout modifiers can also affect a view’s flexibility by either inheriting or overriding it. Some modifiers, such as padding inherit child's flexibility. For instance, if a partially flexible view like a UILabel has padding applied, it remains partially flexible, adjusting its size based on its content and the proposed size, but with extra padding around it. On the other hand, certain modifiers like frame can override the view's flexibility. When a frame is applied to any view, it makes it behave as an inflexible element. diff --git a/Sources/QuickLayout/docs/docs/concepts/layout-trees.mdx b/Sources/QuickLayout/docs/docs/concepts/layout-trees.mdx new file mode 100644 index 0000000..426c4bc --- /dev/null +++ b/Sources/QuickLayout/docs/docs/concepts/layout-trees.mdx @@ -0,0 +1,94 @@ +--- +id: layout-trees +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Element Tree + +It's important to understand how the code written with QuickLayout translates into an `element tree`. +Each view in the tree can have modifiers applied to it, such as padding or frame, which ultimately affect the view's layout and appearance. These modifiers are applied in the order they appear with each modifier wrapping the previous element with a new element. This means the order in which modifiers are written can alter layout and sizing behaviour. + +For example, consider the following view hierarchy: + +```swift +let colorView = UIView() + +var body: Layout { + colorView + .padding(16) + .frame(width: 100, height: 100) +} +``` + +Here, the colorView is first wrapped in a padding modifier, which adds some space around it. Then, the entire padded colorView is wrapped inside a frame modifier. The image below demonstrates the `elements tree` and `layout`: + + +

+ + +If the order of the padding and the frame modifier is swapped, as in the sample below: + +```swift +let colorView = UIView() + +var body: Layout { + colorView + .frame(width: 100, height: 100) + .padding(16) +} +``` + +the element tree will be different and the colorView will have size 100x100: + + +

+ +:::caution Order of modifiers is important +Order of layout modifiers is important and may result in different layouts. +::: + +Let's now consider the following layout with a VStack: + +```swift +/// Note: each colorView is a plain UIView. +var body: Layout { + VStack { + colorView1 + colorView2 + colorView3 + } + .frame(width: 100, height: 100) +} +``` + +The layout above will make each color view have the same size 100x33.33: + + +

+ + +Although, when two colorViews are nested into another VStack, as in the sample below: + +```swift +/// Note: each colorView is a plain UIView. +var body: Layout { + VStack { + colorView1 + VStack { + colorView2 + colorView3 + } + } + .frame(width: 100, height: 100) +} +``` + +The `element tree` and the final view sizes will be completely different: + + +

+ +:::caution Remember +Avoid using Stacks for code organization, because it may lead to a different layout. +::: diff --git a/Sources/QuickLayout/docs/docs/concepts/proposed-size.mdx b/Sources/QuickLayout/docs/docs/concepts/proposed-size.mdx new file mode 100644 index 0000000..7e85a67 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/concepts/proposed-size.mdx @@ -0,0 +1,81 @@ +--- +id: proposed-size +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Proposed Size + +Conceptually, each element in the layout tree proposes a size to its child, and the child reports an intrinsic size. However, in practice, things are more nuanced because each element will have a different algorithm for layout. Let's take a look at how the Frame and Padding elements handle the proposed size. First, we would need to set up the following LayoutNode and Element protocols. + +```swift +struct LayoutNode { + let size: CGSize + let children: [LayoutNode] +} + +protocol Element { + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode +} +``` + +Note that `quick_layoutThatFits` is very similar to `UIView.sizeThatFits`, the difference is that instead of returning CGSize it returns a tree of LayoutNodes. Each node will contain a size of an element. + + +### Frame + +A basic Frame element in QuickLayout would look as follows: + +```swift +struct FrameElement: Element { + + private let child: Element + private let width: CGFloat + private let height: CGFloat + + /// 1. The frame element ignores the proposed size + /// 2. It creates a new proposed size based on its width and height. + /// 3. The child is being proposed the size of the frame. + /// 4. It uses width and height as it's own size. + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let frameSize = CGSize(width: width, height: height) + let childNode = child.quick_layoutThatFits(frameSize) + return LayoutNode(frameSize, [childNode]) + } +} +``` + +In reality, the Frame class is a bit more involved (see `FixedFrameElement.swift`). However, the snippet demonstrates why a frame is often described as a `picture frame`: it ignores the proposed size, and the child element is still able to choose its own size. + +### Padding + +Here is how a basic padding would be implemented: + +```swift +struct PaddingElement: Element { + + private let child: Element + private let insets: UIEdgeInsets + + /// 1. Padding constructs a new proposed size by subtracting the + /// inset from proposedSize. + /// 2. The child element resolves its intrinsic size within the new proposed size. + /// 3. The padding element acquires the size of the child plus the inset. + public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode { + let newProposedSize = CGSize( + width: proposedSize.width - insets.left - insets.right, + height: proposedSize.height - insets.top - insets.bottom + ) + + let childNode = child.quick_layoutThatFits(newProposedSize) + + let paddingSize = CGSize( + width: childNode.size.width + insets.left + insets.right, + height: childNode.size.height + insets.top + insets.bottom + ) + return LayoutNode(size: paddingSize, children: [childNode]) + } +} +``` + +Note that unlike in Frame element, the size of the padding modifier depends on the size of the child. diff --git a/Sources/QuickLayout/docs/docs/concepts/thread-safety.mdx b/Sources/QuickLayout/docs/docs/concepts/thread-safety.mdx new file mode 100644 index 0000000..00b94ee --- /dev/null +++ b/Sources/QuickLayout/docs/docs/concepts/thread-safety.mdx @@ -0,0 +1,31 @@ +--- +id: thread-safety +--- + +# Thread Safety + +QuickLayout is designed to be a powerful and flexible layout engine, allowing you to create complex layouts with ease. +One of the key benefits of QuickLayout is its thread safety, which enables you to use it in a variety of scenarios, including concurrent programming. + +### Layout Algorithm Thread Safety +The layout algorithm itself, including `VStack`, `HStack`, `frame`, `padding`, and other layout functions, are thread safe. +However, the overall threadsafety of any layout operation depends on the threadsafety of leaf elements used in your layout hierarchy. +If your layout contains `UIView` instances, the `sizeThatFits` and `applyFrame` methods can only be used on the main thread which means layout overall has to be used on the main thread and attempting to do otherwise will result in a runtime error. + + + +### Integrating QuickLayout with ComponentUI +If you need to integrate QuickLayout with ComponentUI and require a thread-safe way of creating leaf elements, +you can define static functions that use QuickLayout, but instead of views, use instances of **thread-safe** ViewProxies. +This approach allows you to create layouts that can be safely accessed from any thread. See `CodeFormattingView.swift` and D71543863 for sample integration. + +### Use FOALabel for Thread-Safe Text Measurements +For thread-safe text measurements, we recommend using `FOALabel` instead of `UILabel.proxy`. `FOALabel` provides a thread-safe way to measure text sizes, making it an ideal choice for concurrent programming scenarios. +By following these guidelines and best practices, you can ensure that your QuickLayout-based layouts are thread safe and performant, even in the most demanding concurrent programming environments. + + + +:::warning UILabel.Proxy is Not Thread Safe +When using ViewProxies, keep in mind that UILabel.proxy is not thread safe. +This is because it uses a shared instance of UILabel to calculate size, which can lead to crashes or unexpected behavior when accessed from multiple threads. +::: diff --git a/Sources/QuickLayout/docs/docs/how-to-use/quick-layout-macro.mdx b/Sources/QuickLayout/docs/docs/how-to-use/quick-layout-macro.mdx new file mode 100644 index 0000000..364361d --- /dev/null +++ b/Sources/QuickLayout/docs/docs/how-to-use/quick-layout-macro.mdx @@ -0,0 +1,122 @@ +--- +id: macro-layout-integration +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Quick Layout Macro + +QuickLayout supports fast, automatic layout management for UIKit views. This is achieved by prepending `@QuickLayout` to your view's declaration. +`@QuickLayout` supports a number of features, including automatic layout, subview management, and sizing. It is the recommended +way of integrating QuickLayout, although manual integration is still available when needed. + + + +:::tip Note +For Instagram libraries declared with ig_apple_library, `@QuickLayout` is present whenever the `QuickLayout` module is. For other app libraries +or targets, you must manually import the macro dependencies in your BUCK target: + +``` +swift_macro_deps = ["//fbobjc/Libraries/MobileUI/QuickLayout:QuickLayoutMacro"], +``` +::: + + + +The snippet below demonstrates a simple `@QuickLayout` view: + +```swift +import QuickLayout + +@QuickLayout +final class MyView: UIView { + + private let label = { + let label = UILabel() + label.text = "Hello World!" + return label + }() + + private let image = { + let image = UIImageView() + image.image = UIImage(systemName: "globe.americas") + return image + }() + + var body: Layout { + HStack { + label + Spacer(8) + image + } + } +} +``` + +:::tip Note +Views that utilize @QuickLayout will automatically manage their view hierarchy. Therefore, there is no need to call addSubviews/removeFromSuperview manually. +::: + +### Stateful View +When the state of a view changes, call `setNeedsLayout`. This will prompt the view to update its hierarchy, animate any added or removed views, and re-layout all subviews. The snippet below demonstrates an example of a stateful view. It lazily creates the `label` view when the user presses the `increaseButton` for the first time and updates the layout accordingly. + +```swift +@QuickLayout +final class CounterView: UIView { + + private var count = 0 + + private lazy var label { + // This view will only be created when the count is set to 1 for the first time. + UILabel() + } + + private lazy var increaseButton = { + let button = UIButton(type: .system) + button.setTitle("Increase", for: .normal) + button.addTarget(self, action: #selector(addCount), for: .touchUpInside) + return button + }() + + private lazy var resetButton = { + let button = UIButton(type: .system) + button.setTitle("Reset", for: .normal) + button.addTarget(self, action: #selector(resetCount), for: .touchUpInside) + return button + }() + + var body: Layout { + VStack(spacing: 8) { + if count > 0 { + label + } + HStack(spacing: 8) { + increaseButton + resetButton + } + } + } + + @objc private func addCount() { + count += 1 + label.text = "Count: \(count)" + setNeedsLayout() + } + + @objc private func resetCount() { + count = 0 + setNeedsLayout() + } +} +``` + +### Advanced @QuickLayout Usage +It is possible to continue overriding `layoutSubviews` and even `sizeThatFits` while utilizing the @QuickLayout macro. When doing so: + +- `layoutSubviews` will perform `body` layout directly after `super.layoutSubviews()` is called. +- `sizeThatFits` will override the default body sizing, allowing you to return something fully custom. + +Additionally, the following methods are available to override: + +- `isBodyEnabled`: this allows you to toggle automatic layout management. It's a great way to test a migration to QuickLayout in-place. +- `bodyContainerView`: this changes the view that subviews are managed within. Collection view and table view cells utilize this to ensure views are placed on `contentView`. diff --git a/Sources/QuickLayout/docs/docs/how-to-use/quick-layout-without-macro.mdx b/Sources/QuickLayout/docs/docs/how-to-use/quick-layout-without-macro.mdx new file mode 100644 index 0000000..a8acdc1 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/how-to-use/quick-layout-without-macro.mdx @@ -0,0 +1,52 @@ +--- +id: manual-layout-integration +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Manual Layout Integration + +The @QuickLayout macro is available for all view instances, but not view controllers. When needed, you can still use QuickLayout without this macro. In that case, you will need to manually manage the view hierarchy and override the `layoutSubviews` and `sizeThatFits` methods. The snippet below demonstrates a view that does not utilize the @QuickLayout macro, but still utilizes a declarative syntax for layout. +```swift +import QuickLayout + +final class MyView: UIView { + + private let label = { + let label = UILabel() + label.text = "Hello World!" + return label + }() + + private let image = { + let image = UIImageView() + image.image = UIImage(systemName: "globe.americas") + return image + }() + + init() { + super.init(frame: .zero) + self.addSubviews { + label + image + } + } + + var body: Layout { + HStack { + label + Spacer(8) + image + } + } + + override func layoutSubviews() { + super.layoutSubviews() + body.applyFrame(bounds) + } + + override func sizeThatFits(_ proposedSize: CGSize) { + body.sizeThatFits(proposedSize) + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/intro.mdx b/Sources/QuickLayout/docs/docs/intro.mdx new file mode 100644 index 0000000..9300ae5 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/intro.mdx @@ -0,0 +1,69 @@ +--- +sidebar_position: 1 +id: intro +slug: / +--- + +# Overview + +QuickLayout is a declarative layout library for iOS, designed to work seamlessly with UIKit. It is lightning-fast, incredibly simple to use, and offers a powerful set of modern layout primitives. + +QuickLayout's API will feel natural to many iOS engineers. With a small and well tested codebase, it is production ready. QuickLayout has significant usage across many of Instagram's core features. + +### Code Sample + +```swift +import QuickLayout + +@QuickLayout +class MyCellView: UIView { + + let titleLabel = UILabel() + let subtitleLabel = UILabel() + + var body: Layout { + HStack { + VStack(alignment: leading) { + titleLabel + Spacer(4) + subtitleLabel + } + Spacer() + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + } +} +``` + +Above is a fully functional view. Subviews are managed automatically, layout is performed at the correct time, and sizeThatFits returns an accurate value. + +### Features + +- ⭐️ **Lightning-Fast**: Custom layout engine for blazing fast performance—no Auto Layout or Flexbox overhead. +- 🧩 **Modern Layout Primitives**: Includes `HStack`, `VStack`, `ZStack`, `Grid`, and `Flow` as lightweight, pure Swift structs that don’t add extra views. +- 🚀 **Easy Integration**: Simple @QuickLayout macro for fast setup and automatic subview management. +- 🧵 **Thread-Safe**: [With some caveats](https://facebookincubator.github.io/QuickLayout/concepts/thread-safety/), our API can be used concurrently and off the main thread. + +### Motivation +Declarative frameworks like SwiftUI have gained significant popularity in UI development due to their simplicity and expressive syntax. However, adopting these frameworks often requires developers to commit fully, leaving behind UIKit, which powers countless existing apps. This transition can be daunting, costly, and sometimes impractical for projects deeply embedded in UIKit. + +QuickLayout is a new declarative library that brings the power of declarative syntax into the imperative world of UIKit. It is designed to work hand-in-hand with UIKit, ensuring you can easily mix and match declarative and imperative code. This hybrid approach means you can maintain your existing views while gradually transitioning to a more declarative style. + + + +### The Showcase App + +To see `QuickLayout` in action you can run the following command in `fbsource`: + +```bash +arc showcase QuickLayout +``` + +Open `HelloWorldView.swift` file and activate the preview. You can update the layout without having to run the app. + + +### Links +[Proposal](https://docs.google.com/document/d/15sdPtb2GecwcLSWNAVkH588Z6OtuCMBEpuDGhLE9KcM/edit) | [RFC](https://docs.google.com/document/d/1d4B9J5qnBMIy6ho7VAIhWmQxqEGoEl_NDQ370KYF0cw/edit) | [Workplace Group](https://fb.workplace.com/groups/quicklayout) + + diff --git a/Sources/QuickLayout/docs/docs/layout/alignment-guides.mdx b/Sources/QuickLayout/docs/docs/layout/alignment-guides.mdx new file mode 100644 index 0000000..5a61b81 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/alignment-guides.mdx @@ -0,0 +1,68 @@ +--- +id: alignment-guides +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Alignment Guides + +```swift +extension Element { + func alignmentGuide(_ horizontalAlignment: HorizontalAlignment, computeValue: @escaping @Sendable (ViewDimensions) -> CGFloat) -> Element + func alignmentGuide(_ verticalAlignment: VerticalAlignment, computeValue: @escaping @Sendable (ViewDimensions) -> CGFloat) -> Element +} +``` + +Alignment guides allow you to control how views align within a container like `HStack`, `VStack`, or `ZStack`. +By default, these stacks align their children based on standard alignment values (like `.center`, `.leading`, or `.top`). +However, you can use alignment guides to customize alignment behavior by defining your own rules for how each child should be positioned. + +:::caution Propagation +Of note, alignment guides for default types are not recognized beyond the first multi-child container, however custom alignment guides +are. If you need complex alignment behavior spanning more than one tier of layout +container, create a custom alignment type for full control here. The example below does this. +::: + +### Examples + +```swift +extension VerticalAlignment { + private struct EmojiTitleAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[VerticalAlignment.bottom] + } + } + static let emojiTitle = VerticalAlignment( + EmojiTitleAlignment.self + ) +} + +var body: Layout { + VStack(spacing: 30) { + titleView + HStack(alignment: .emojiTitle, spacing: 16) { + for emojiViewData in emojiViews { + VStack { + emojiViewData.emojiView + emojiViewData.emojiTitleView + .alignmentGuide(.emojiTitle) { _ in 0 } + emojiViewData.emojiDescriptionView + } + } + } + } +} +``` + + +

+ + +### Use Cases + +Alignment guides are a great tool to use when a particular alignment type should mean something slightly different for your view. +Perhaps your view has some decoration beneath the focal element, but `.bottom` alignment should apply to the focal element and not +the content below it. + +Or, perhaps you'd like `.center` horizontal alignment, but some views need adjustment to match this behavior. While this is possible +via other means, specifying alignment guides can be a natural, easy way to accomplish this. diff --git a/Sources/QuickLayout/docs/docs/layout/aspect-ratio.mdx b/Sources/QuickLayout/docs/docs/layout/aspect-ratio.mdx new file mode 100644 index 0000000..ca03dd2 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/aspect-ratio.mdx @@ -0,0 +1,30 @@ +--- +id: aspect-ratio +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Aspect Ratio + +```swift +extension Element { + func aspectRatio(_ ratio: CGSize, contentMode: ContentMode = .fit) -> Element +} +``` + +Constrains the view’s dimensions to an aspect ratio specified by a CGSize. +If this view is resizable, it uses aspectRatio as its own aspect ratio. + +### Examples + +```swift +var body: Layout { + VStack { + imageView + .resizable() + .aspectRatio(CGSize(width: 3, height: 2)) + Spacer(8) + label + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/background.mdx b/Sources/QuickLayout/docs/docs/layout/background.mdx new file mode 100644 index 0000000..8a02c9d --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/background.mdx @@ -0,0 +1,27 @@ +--- +id: background +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Background + +```swift +extension Element { + func background(alignment: Alignment = .center, content: () -> Element?) -> Element +} +``` + +Positions the content element within the frame of the target element. +The modifier measures the target element, proposes the target element's size to the content element, and then aligns it within the frame of the target element. + +### Examples + +```swift +var body: Layout { + profilePhoto + .background { + backgroundView + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/calculate-size-label.mdx b/Sources/QuickLayout/docs/docs/layout/calculate-size-label.mdx new file mode 100644 index 0000000..181a13b --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/calculate-size-label.mdx @@ -0,0 +1,40 @@ +--- +id: calculate-size-label +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Calculate size of a Label + +There are many options to calculate the size of a label, though none is perfect. + +## `UILabel.textRectForBounds` + +This method respects the constraining size. However, Apple docs say that it shouldn't be called directly. +Moreover, it doesn't perform well when compared to sizeThatFits. + + + +## `NSString.boundingRectWithSize:options:attributes:context:` + +According to rumors the size returned by this method may not match the size needed for UILabel. +For instance, UILabel may add extra inset or adjust line spacing to fit larger characters (e.g. emojis). + +## `UILabel.sizeThatFits` + +The method appears to do some caching to optimize repetitive calls with the same constraning size. + + + +It will return the desired size for the label, however with no respect the constraining size. +For instance when numberOfLines is set to 1, UILabel will ignore the width constraint. +When numberOfLines is set to 0, UILabel will ignore the height constraint. + +## Label layout implementation in QuickLayout + +For Labels' layout implementation, the measured size is clamped with the constraining size +to ensure the label doesn't grow beyond the proposed size. +This is OK but not ideal because clamping wouldn't round to the nearest visible line +leading to extra space on top and bottom. + + diff --git a/Sources/QuickLayout/docs/docs/layout/condition-for-each.mdx b/Sources/QuickLayout/docs/docs/layout/condition-for-each.mdx new file mode 100644 index 0000000..6d6c14f --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/condition-for-each.mdx @@ -0,0 +1,29 @@ +--- +id: foreach +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# ForEach + + +### ForEach + +```swift +var body: Layout { + VStack(spacing: 8) { + ForEach(labels) + } +} +``` + +```swift +var body: Layout { + VStack(spacing: 8) { + ForEach(labels) { label in + label + .frame(height: 40) + } + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/condition-for-loop.mdx b/Sources/QuickLayout/docs/docs/layout/condition-for-loop.mdx new file mode 100644 index 0000000..39d1dc4 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/condition-for-loop.mdx @@ -0,0 +1,17 @@ +--- +id: for-loops +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# For Loop + +```swift +var body: Layout { + VStack(spacing: 8) { + for label in labels { + label + } + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/condition-if-else.mdx b/Sources/QuickLayout/docs/docs/layout/condition-if-else.mdx new file mode 100644 index 0000000..e2bdb36 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/condition-if-else.mdx @@ -0,0 +1,27 @@ +--- +id: if-else +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# If...else + + +

+ +Stacks result builders support conditional layouts, for-loops and `ForEach`. + + +### If...Else + +```swift +var body: Layout { + HStack { + if somethingTrue { + icon1 + } else { + icon2 + } + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/custom-alignments.mdx b/Sources/QuickLayout/docs/docs/layout/custom-alignments.mdx new file mode 100644 index 0000000..582bc6b --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/custom-alignments.mdx @@ -0,0 +1,72 @@ +--- +id: custom-alignments +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Custom Alignments + +```swift +struct MyCustomAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat +} +``` + +Custom alignment allows you to define your own alignment guides for arranging views in a layout—going beyond built-in options like .leading, .center, .trailing, etc. +It gives you precise control over how views line up relative to each other in containers like HStack, VStack, or ZStack. + +## Example + +```swift +struct FirstThirdAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height / 3 + } +} + +extension VerticalAlignment { + static let firstThirdAlignment = VerticalAlignment(FirstThirdAlignment.self) +} + +@QuickLayout +final class CustomAlignmentView: UIView { + + private let colorViews = (0...8).map { index in + let view = UIView() + view.backgroundColor = .systemBlue + return view + } + + private let labels = (0...8).map { index in + let label = UILabel() + label.text = "\(index)" + label.textColor = .white + label.font = .monospacedDigitSystemFont(ofSize: 18, weight: .medium) + return label + } + + var body: Layout { + HStack(alignment: .firstThirdAlignment, spacing: 2) { + VStack(spacing: 2) { + colorViews[0].overlay { labels[0] } + colorViews[1].overlay { labels[1] } + colorViews[2].overlay { labels[2] } + }.frame(height: 140) + VStack(spacing: 2) { + colorViews[3].overlay { labels[3] } + colorViews[4].overlay { labels[4] } + colorViews[5].overlay { labels[5] } + }.frame(height: 250) + VStack(spacing: 2) { + colorViews[6].overlay { labels[6] } + colorViews[7].overlay { labels[7] } + colorViews[8].overlay { labels[8] } + }.frame(height: 180) + } + .padding(20) + } +} +``` + + +

diff --git a/Sources/QuickLayout/docs/docs/layout/empty-layout.mdx b/Sources/QuickLayout/docs/docs/layout/empty-layout.mdx new file mode 100644 index 0000000..1609d88 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/empty-layout.mdx @@ -0,0 +1,14 @@ +--- +id: empty-layout +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Empty Layout + +```swift +func EmptyLayout() -> Element +``` + +Null object pattern. Sizes to zero. +

diff --git a/Sources/QuickLayout/docs/docs/layout/expandable.mdx b/Sources/QuickLayout/docs/docs/layout/expandable.mdx new file mode 100644 index 0000000..e575f65 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/expandable.mdx @@ -0,0 +1,27 @@ +--- +id: expandable +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Expand By + +```swift +extension UIView { + func expand(by size: CGSize) -> Element +} +``` +Measures the intrinsic size of the view and then adds the additional size. + +### Examples + +```swift +var body: Layout { + HStack { + Spacer() + button + .expand(by: CGSize(width: 8, height: 8)) + Spacer() + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/fixed-frame.mdx b/Sources/QuickLayout/docs/docs/layout/fixed-frame.mdx new file mode 100644 index 0000000..1607d24 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/fixed-frame.mdx @@ -0,0 +1,47 @@ +--- +id: fixed-frame +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Fixed Frame + + +

+ +```swift +extension Element { + func frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center) -> Element +} +``` + +The Fixed Frame positions its child element inside an invisible container of a given size. +If you omit a size (or pass nil), the frame will use the sizing behavior of its child element for that axis. +When using this method, you must provide at least one size constraint. + +:::caution Frame doesn't change the size of the view + +The `Frame` modifier does not directly change the size of its child view but acts like a "picture frame." It proposes the size to the child view, but the child may choose to have a smaller size. Therefore, the `Frame` modifier uses the `alignment` parameter to position the child within its own size. +::: + +:::tip Use resizable modifier + +To enforce a view uncoditionally acquire the size of the frame, wrap it with the `resizable` modifier. +```swift +var body: Layout { + HStack { + faceImageView + .resizable() + .frame(width: 40, height: 40) + label + } +} +``` +::: + +### Alignment + +The frame allows you to control the position of the child view within it's space with the `alignment` parameter. +See the image below demonstrating the frame with various alignments and labels. + + diff --git a/Sources/QuickLayout/docs/docs/layout/fixed-size.mdx b/Sources/QuickLayout/docs/docs/layout/fixed-size.mdx new file mode 100644 index 0000000..0226aec --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/fixed-size.mdx @@ -0,0 +1,34 @@ +--- +id: fixed-size +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Fixed Size + +```swift +extension Element { + func fixedSize(axis: AxisSet = [.horizontal, .vertical]) -> Element +} +``` + +The fixed size modifier proposes the child view an infinite size, allowing it to expand freely to its intrinsic size without any constraints. +By specifying an axis, the modifier applies infinity along that single axis. + +```swift +var body: Layout { + VStack { + label1 + .frame(width: 100, height: 100) + label2 + .fixedSize() + .frame(width: 100, height: 100) + } +} +``` +In the snippet above, The `label2` will be sized to its intrinsic size, ignoring frame 100x100. +See the image below for the resulting layout: + +
+ +
diff --git a/Sources/QuickLayout/docs/docs/layout/flexible-frame.mdx b/Sources/QuickLayout/docs/docs/layout/flexible-frame.mdx new file mode 100644 index 0000000..fc8db60 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/flexible-frame.mdx @@ -0,0 +1,39 @@ +--- +id: flexible-frame +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Flexible Frame + +```swift +extension Element { + func frame( + minWidth: CGFloat? = nil, + maxWidth: CGFloat? = nil, + minHeight: CGFloat? = nil, + maxHeight: CGFloat? = nil, + alignment: Alignment = .center + ) -> Element & Layout +} +``` + +The Flexible Frame positions its child element inside an invisible flexible container of a given size constraints. +If you omit a constraint (or pass nil), the frame will use the sizing behavior of its child element for that axis. +When using this method, you must provide at least one size constraint. + +:::caution Flexible Frame doesn't change the size of the view +The `Flexible Frame` modifier does not directly change the size of its child view but acts like a "picture frame." It proposes the size to the child view, but the child may choose to have a smaller size. Therefore, the `Flexible Frame` modifier uses the `alignment` parameter to position the child within its own size. +::: + +### Examples + +```swift +var body: Layout { + HStack { + label1 + .frame(minWidth: 40, maxWidth: 80) + label2 + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/grid.mdx b/Sources/QuickLayout/docs/docs/layout/grid.mdx new file mode 100644 index 0000000..8ebf2ad --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/grid.mdx @@ -0,0 +1,272 @@ +--- +id: grid +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + + +# Grid + + +

+ + +A grid is a layout container that arranges views into a table-like structure with rows and columns. + +```swift +public func Grid( + alignment: Alignment = .center, + horizontalSpacing: CGFloat = 0, + verticalSpacing: CGFloat = 0, + @FastArrayBuilder rows: () -> [GridRowElement] = { [] }, +) -> Element & Layout + +public func GridRow( + alignment: VerticalAlignment? = nil, + @FastArrayBuilder children: () -> [Element] +) -> GridRowElement +``` + +```swift +var body: Layout { + Grid { + GridRow { + view1 + view2 + view3 + } + GridRow { + view4 + view5 + view6 + } + } +} +``` + +## Alignment + + +Each grid view has two alignment properties: horizontal and vertical alignment. This controls the position of the view within its cell. + +There are four main alignment types: + +`Grid Alignment` - +Sets the initial horizontal and vertical alignment for all views within the grid, which is `.center` by default. + +`Row Alignment` - + Overrides the vertical alignment for every view in a specific row. + +`Column Alignment` - + Overrides the horizontal alignment for views in a specific column. + +`Grid Cell Anchor Alignment` - + Overrides both the horizontal and vertical alignment for an individual view. + + + +### Grid Alignment + +This serves as the default alignment for all views and is the lowest-priority alignment. It is applied to the grid as a whole and determines how every view within the grid is aligned within its own cell. + +```swift +var body: Layout { + Grid(alignment: .top) { + GridRow { + view1 + view2 + view3 + } + GridRow { + view4 + view5 + view6 + } + } +} +``` + + +

+ + + + +### Row Alignment + +Row alignment overrides the vertical alignment for every view in a row. This is the second-highest priority alignment, and it overrides the grid's vertical alignment. + +```swift +var body: Layout { + Grid { + GridRow { + view1 + view2 + view3 + } + GridRow(alignment: .bottom) { + view4 + view5 + view6 + } + } +} +``` + + + +

+ +### Column Alignment + +Column alignment overrides the horizontal alignment for every view in a column. This is also the second-highest priority alignment, and it overrides the grid's horizontal alignment. + +```swift +var body: Layout { + Grid { + GridRow { + view1 + .gridColumnAlignment(.leading) + view2 + } + GridRow { + view3 + view4 + } + GridRow { + view5 + view6 + } + } +} +``` + +

+ + +### Grid Cell Anchor Alignment + +Grid cell anchor alignment applies a horizontal and vertical alignment to a specific view in a grid. This is the highest priority alignment, and it overrides both the horizontal amd vertical alignment of an view. + +```swift +var body: Layout { + Grid { + GridRow { + view1 + view2 + } + GridRow { + view3 + view4 + .gridCellAnchor(.bottomTrailing) + } + } +} +``` + + +

+ +Alternatively, you can specify a custom `UnitPoint` to position the view to a specific point in the cell. + + + +```swift +var body: Layout { + Grid { + GridRow { + view1 + view2 + } + GridRow { + view3 + view4 + .gridCellAnchor(UnitPoint(x: 0.5, y: 1.0)) + } + } +} +``` + + +

+ +### Spacing + +A fixed amount of spacing can be added between rows and columns using the `verticalSpacing` parameter for spacing between rows and the `horizontalSpacing` parameter for spacing between columns. + + +```swift +var body: Layout { + Grid(verticalSpacing: 8) { + GridRow { + view1 + view2 + view3 + } + GridRow { + view4 + view5 + view6 + } + } +} +``` + + +

+ +```swift +var body: Layout { + Grid(horizontalSpacing: 8) { + GridRow { + view1 + view2 + view3 + } + GridRow { + view4 + view5 + view6 + } + } +} +``` + + +

+ + +`verticalSpacing` and `horizontalSpacing` can be used together. + + + + +```swift +var body: Layout { + Grid(horizontalSpacing: 8, verticalSpacing: 8) { + GridRow { + view1 + view2 + view3 + } + GridRow { + view4 + view5 + view6 + } + } +} +``` + + + +

+ +### Performance Considerations +Grid may measure cells up to 3 times. +Second measurement occures when a text needs to be broken into multiple lines. +Third measurement occures when a multiline text is truncated. + + +:::note +Grid layout does not support `gridCellColumns` and `gridCellUnsizedAxis` modifiers. diff --git a/Sources/QuickLayout/docs/docs/layout/hflow.mdx b/Sources/QuickLayout/docs/docs/layout/hflow.mdx new file mode 100644 index 0000000..d5900e2 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/hflow.mdx @@ -0,0 +1,131 @@ +--- +id: hflow +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# HFlow + +

+ +HFlow is a multi-line HStack that starts a new line every time elements don't fit in the bounding space. + + +```swift +public func HFlow( + itemAlignment: VerticalAlignment = .center, + lineAlignment: HorizontalAlignment = .center, + itemSpacing: CGFloat = 0, + lineSpacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) -> Element & Layout +``` + +```swift +var body: Layout { + HFlow { + icon + label + Button + } +} +``` + + +### Item Alignment + +Items within a line can be aligned vertically using the `itemAlignment` parameter. The default value is `.center` + + + + +
+ +
+ + +```swift +var body: Layout { + HFlow(itemAlignment: .center) { + icon + label + Button + } +} +``` + +### Line Alignment + +Similarily, lines can be aligned horizontally using the `lineAlignment` parameter. The default value is `.center` + + + + +
+ +
+ + +```swift +var body: Layout { + HFlow(lineAlignment: .leading) { + icon + label + Button + } +} +``` + +### Spacing + +A fixed amount of spacing can be added between each item using the `itemSpacing` parameter or betweeen each line using the `lineSpacing` parameter. The default value is 0. + + +

+ +```swift +var body: Layout { + HFlow(itemSpacing: 8) { + icon + label + Button + } +} +``` + + + +

+ + +```swift +var body: Layout { + HFlow(lineSpacing: 8) { + icon + label + Button + } +} +``` + +Like all other parameters, itemSpacing and lineSpacing can be combined. + + + +```swift +var body: Layout { + HFlow(itemSpacing: 8, lineSpacing: 8) { + icon + label + Button + } +} +``` + +### Performance Considerations + +HFlow measures each child element exactly once. + +:::note +Spacer Element cannot be used with HFlow +::: diff --git a/Sources/QuickLayout/docs/docs/layout/hstack.mdx b/Sources/QuickLayout/docs/docs/layout/hstack.mdx new file mode 100644 index 0000000..c6ad941 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/hstack.mdx @@ -0,0 +1,107 @@ +--- +id: hstack +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# HStack + + +

+ +```swift +func HStack( + alignment: VerticalAlignment = .center, + spacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) +``` + +HStack arranges child elements horizontally and acquires the size of all children. + +```swift +var body: Layout { + HStack { + icon + label + Spacer() + } +} +``` + + +### Alignment + + +

+You can control alignment of elements within stack with the `alignment` parameter: + + +```swift +var body: Layout { + HStack(alignment: .top) { + view1 + view2 + view3 + } +} +``` + +### Spacing + +A fixed amount of spacing can be added between every element via `spacing` parameter: + + + +```swift +var body: Layout { + HStack(spacing: 8) { + view1 + view2 + view3 + } +} +``` + +### Spacer + +`Spacer()` can be used to specify flexible spacing. The example below adds a flexible spacer between two views that pushes them apart: + + + +```swift +var body: Layout { + VStack { + view1 + Spacer() + view2 + } +} +``` + +`Spacer(x)` can be used to add fixed spacing between views. + + +```swift +var body: Layout { + VStack { + view1 + Spacer(8) + view2 + Spacer(8) + view3 + } +} +``` + +### Performance Considerations + +Up to two measurements may be performed to determine the size of certain child elements. +Fixed-size elements (e.g., .frame() or UIImage) are measured only once. +Fully flexible elements are not measured directly; instead, they are assigned a size based on the available space, which is proportionally distributed among all fully flexible views. + +If a stack contains two or more partially flexible views (e.g., UILabel) and the proposed size is smaller than the intrinsic content size, the child elements may be measured multiple times. +A common indication of double measurements in a HStack is line break in multiline text or truncation in single line text. +When the proposed size is infinite, no double measurement occurs. + +To avoid double measurement, use .layoutPriority() modifier to specify the order in which child elements should be measured. diff --git a/Sources/QuickLayout/docs/docs/layout/ideal-layout.mdx b/Sources/QuickLayout/docs/docs/layout/ideal-layout.mdx new file mode 100644 index 0000000..721a040 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/ideal-layout.mdx @@ -0,0 +1,56 @@ +--- +id: ideal-layout +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Ideal Layout + +```swift +extension StackElement { + func idealLayout() -> Element +} +``` + +:::danger Ideal Layout +When using Ideal Layout, be aware that it will always double measure. +This can lead to frame drops in collection views, so reserve its use for cases where it's absolutely necessary. +::: + + +The ideal layout modifier, comibined with resizable views allows you to create Stacks with equal size views along the cross axis. + +```swift +var body: Layout { + VStack { + logInButton + .resizable(axis: .horizontal) + Spacer(12) + forgotPasswordButton + .resizable(axis: .horizontal) + } + .idealLayout() +} +``` +In the snippet above, the vertical stack will make both buttons have equal width: + +
+ +
+ + +Equal heights layout can be achieved by wrapping views into`.resizable(axis: .vertical)`, HStack and .idealLayout: +```swift +HStack { + shortTextlabel + .resizable(axis: .vertical) + Spacer(12) + longeTextLabel + .resizable(axis: .vertical) +} +.idealLayout() +``` + +
+ +
diff --git a/Sources/QuickLayout/docs/docs/layout/layout-direction.mdx b/Sources/QuickLayout/docs/docs/layout/layout-direction.mdx new file mode 100644 index 0000000..e929ce2 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/layout-direction.mdx @@ -0,0 +1,29 @@ +--- +id: layout-direction +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Layout Direction + +```swift +extension Element { + func layoutDirection(_ direction: LayoutDirection) -> Element & Layout { +} +``` + +QuickLayout supports both left-to-right (LTR) and right-to-left (RTL) directions, enabling seamless content layout across diverse languages and locales. By default, the direction is determined by the user's locale; however, you can utilise the layoutDirection modifier to override this setting for a specific tree of elements. + +### Examples + +```swift +var body: Layout { + HStack { + profilePhoto + Spacer(8) + userName + Spacer() + } + .layoutDirection(.rightToLeft) +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/layout-priority.mdx b/Sources/QuickLayout/docs/docs/layout/layout-priority.mdx new file mode 100644 index 0000000..a51e74d --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/layout-priority.mdx @@ -0,0 +1,36 @@ +--- +id: layout-priority +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Layout Priority + +```swift +extension Element { + func layoutPriority(_ priority: CGFloat) -> Element +} +``` + +Sets the priority by which a parent V/HStack should allocate space to this element. +A view's default priority is 0, which distributes available space evenly to all sibling views. +Set a higher priority to measure the view first with a larger portion of available space. + + +### Examples + +```swift +var body: Layout { + HStack { + profilePhoto + .aspectRatio(CGSize(width: 1, height: 1)) + .layoutPriority(1) + Spacer(8) + titleLabel + Spacer(8) + bodyLabel + Spacer() + button + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/offset.mdx b/Sources/QuickLayout/docs/docs/layout/offset.mdx new file mode 100644 index 0000000..fa6db7d --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/offset.mdx @@ -0,0 +1,33 @@ +--- +id: offset +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Offset + + +

+ +```swift +extension Element { + func offset(x: CGFloat = 0, y: CGFloat = 0) -> Element +} +``` + +Offsets the child element by the specified amount. + +### Examples + +```swift +var body: Layout { + HStack { + faceView1 + faceView2 + .offset(y: -30) + faceView3 + faceView4 + faceView5 + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/overlay.mdx b/Sources/QuickLayout/docs/docs/layout/overlay.mdx new file mode 100644 index 0000000..b2fcad9 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/overlay.mdx @@ -0,0 +1,31 @@ +--- +id: overlay +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Overlay + +```swift +extension Element { + func overlay(alignment: Alignment = .center, content: () -> Element?) -> Element +} +``` + +Positions the content element within the frame of the target element. +The modifier measures the target element, proposes the target element's size to the content element, and then aligns it within the frame of the target element. + +### Examples + +```swift +var body: Layout { + VStack { + profilePhoto + .overlay { + button.resizable() + } + Spacer(8) + label + } +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/padding.mdx b/Sources/QuickLayout/docs/docs/layout/padding.mdx new file mode 100644 index 0000000..a1572df --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/padding.mdx @@ -0,0 +1,85 @@ +--- +id: padding +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Padding + + +

+ +```swift +extension Element { + func padding(_ length: CGFloat) -> Element + func padding(_ insets: EdgeInsets) -> Element + func padding(_ edges: EdgeSet = .all, _ length: CGFloat) -> Element +} +``` + +Padding adds the desired amount of space to the edges of the child element. + +### Examples + +```swift +var body: Layout { + VStack { + label1 + .padding(8) + + label2 + .padding(.leading, 8) + + label3 + .padding(.horizontal, 8) + + label4 + .padding([.leading, .bottom], 8) + + label5 + .padding([.horizontal, .bottom], 8) + + label6 + .padding(.horizontal, 8) + .padding(.vertical, 16) + + label5 + .padding(EdgeInsets(top: 10, leading: 20, bottom: 30, trailing: 40)) + } +} +``` + +### Edge insets + +The inset distances for the sides of an element. + +```swift +struct EdgeInsets { + + var top: CGFloat + var bottom: CGFloat + var leading: CGFloat + var trailing: CGFloat + + static let zero = EdgeInsets() + + init() + init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) +} +``` + +### Edge Set + +A set of edges. + +```swift +struct EdgeSet: OptionSet { + static let all: EdgeSet + static let top: EdgeSet + static let bottom: EdgeSet + static let leading: EdgeSet + static let trailing: EdgeSet + static let horizontal: EdgeSet + static let vertical: EdgeSet +} +``` diff --git a/Sources/QuickLayout/docs/docs/layout/resizable.mdx b/Sources/QuickLayout/docs/docs/layout/resizable.mdx new file mode 100644 index 0000000..6cf4347 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/resizable.mdx @@ -0,0 +1,36 @@ +--- +id: resizable +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Resizable + + + +```swift +extension UIView { + func resizable(axis: AxisSet = [.horizontal, .vertical]) -> Element +} +``` + +Makes the view ignore its intrinsic size (size returned by sizeThatFits). +If no axis specified, the target view becomes fully flexible along both axes. If only one axis is specified, the target view intrinsic size is preserved along the other axis. + +The snippet below + +```swift +var body: Layout { + HStack { + faceImageView1 + .frame(width: 40, height: 40) + faceImageView2 + .resizable() + .frame(width: 40, height: 40) + } +} +``` + +will result in the following layout: + + diff --git a/Sources/QuickLayout/docs/docs/layout/vflow.mdx b/Sources/QuickLayout/docs/docs/layout/vflow.mdx new file mode 100644 index 0000000..15b66ac --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/vflow.mdx @@ -0,0 +1,92 @@ +--- +id: vflow +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + + +# VFlow + +

+ +VFlow is a multi-line VStack that starts a new line every time elements don't fit in the bounding space. + + +```swift +public func VFlow( + itemAlignment: HorizontalAlignment = .center, + lineAlignment: VerticalAlignment = .center, + itemSpacing: CGFloat = 0, + lineSpacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) -> Element & Layout +``` + +```swift +var body: Layout { + VFlow { + icon + label + Button + } +} +``` +## Item Alignment + +Items within a line can be aligned horizontally using the `itemAlignment` parameter. The default value is `.center` + + + +```swift +var body: Layout { + VFlow(itemAlignment: .leading) { + icon + label + Button + } +} +``` + + + +## Line Alignment + +Similarily, lines can be aligned vertically using the `lineAlignment` parameter. The default value is `.center` + + + +```swift +var body: Layout { + HFlow(lineAlignment: .center) { + icon + label + Button + } +} +``` + + +## Spacing + +A fixed amount of spacing can be added between each item using the `itemSpacing` parameter or betweeen each line using the `lineSpacing` parameter. The default value is 0. + + + +```swift +var body: Layout { + HFlow(itemSpacing: 8, lineSpacing: 10) { + icon + label + Button + } +} +``` + + +### Performance Considerations + +VFlow measures each child element exactly once. + +:::note +Spacer Element cannot be used with VFlow +::: diff --git a/Sources/QuickLayout/docs/docs/layout/vstack.mdx b/Sources/QuickLayout/docs/docs/layout/vstack.mdx new file mode 100644 index 0000000..3c2ead6 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/vstack.mdx @@ -0,0 +1,88 @@ +--- +id: vstack +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# VStack + + +

+ +```swift +func VStack( + alignment: HorizontalAlignment = .center, + spacing: CGFloat = 0, + @FastArrayBuilder children: () -> [Element] +) +``` + +VStack arranges child elements vertically and acquires the size of all children. +All child elements are layout even if they are not visible on screen. Use `UICollectionView` or `UITableView` to make infinitly scrollable lists. + +```swift +var body: Layout { + VStack { + view1 + view2 + view3 + } +} +``` + + +### Alignment + + +

+ +You can control alignment of elements within stack with the `alignment` parameter: +```swift +var body: Layout { + VStack(alignment: .leading) { + view1 + view2 + view3 + } +} +``` + +### Spacing + +A fixed amount of spacing can be added between every element via `spacing` parameter: + +```swift +var body: Layout { + VStack(spacing: 10) { + label1 + label2 + } +} +``` + +### Spacer + +A `Spacer` can be used to specify flexible spacing or fixed spacing: + +```swift +var body: Layout { + VStack { + Spacer() + label1 + Spacer(10) + label2 + Spacer() + } +} +``` +### Performance Considerations + +Up to two measurements may be performed to determine the size of certain child elements. +Fixed-size elements (e.g., .frame() or UIImage) are measured only once. +Fully flexible elements are not measured directly; instead, they are assigned a size based on the available space, which is proportionally distributed among all fully flexible views. + +If a stack contains two or more partially flexible views (e.g., UILabel) and the proposed size is smaller than the intrinsic content size, the child elements may be measured multiple times. +A common indication of double measurements in a VStack is truncated multiline text. +When the proposed size is infinite, no double measurement occurs. + +To avoid double measurement, use .layoutPriority() modifier to specify the order in which child elements should be measured. diff --git a/Sources/QuickLayout/docs/docs/layout/zstack.mdx b/Sources/QuickLayout/docs/docs/layout/zstack.mdx new file mode 100644 index 0000000..4cf9268 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/layout/zstack.mdx @@ -0,0 +1,48 @@ +--- +id: zstack +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# ZStack + + +

+ +```swift +func ZStack( + alignment: Alignment = .center, + @FastArrayBuilder children: () -> [Element] +) +``` + +ZStack arranges child elements on top of each other and acquires the size of the largest element. + +```swift +var body: Layout { + ZStack { + background + label + } +} +``` + + +### Alignment + +Control the alignment of elements within the ZStack with the `alignment` property. The following snippets aligns all views to the top leading corner of the ZStack. + + + +```swift +var body: Layout { + ZStack(alignment: .topLeading) { + view1 + view2 + view3 + } +} +``` +### Performance Considerations + +ZStack measures each child element exactly once. diff --git a/Sources/QuickLayout/docs/docs/lazy-views.mdx b/Sources/QuickLayout/docs/docs/lazy-views.mdx new file mode 100644 index 0000000..dea6f2f --- /dev/null +++ b/Sources/QuickLayout/docs/docs/lazy-views.mdx @@ -0,0 +1,71 @@ +--- +id: lazy-views +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import {FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal'; + +# Lazy Views + +LazyView is a design pattern that allows for the delayed initialization of a view until it is actually needed. +This approach improves startup time, scroll performance and business metrics. +QuickLayout's `LazyView` works similar to lazy variables in Swift, but provides a extra features such as `ifLoaded` getter to retrieve the view if it has been loaded. + + + +### Sample Code + +```swift +import QuickLayout + +@QuickLayout +final class MyView: UIView { + + var showReplyButton: Bool = false { + didSet { setNeedsLayout() } + } + + var showDeleteButtonView: Bool = false { + didSet { setNeedsLayout() } + } + + private var replyButton = LazyView { [weak self] in + /// This closure is called when the view is used in the layout for the first time. + let button = UIButton(type: .system) + ... + return button + } + + private var deleteButton = LazyView { [weak self] in + /// This closure is called when the view is used in the layout for the first time. + let button = UIButton(type: .system) + ... + return button + } + + private func updateButtonStateIfNeeded(isEnabled: Bool) { + /// Doesn't trigger the view load. + replyButton.ifLoaded?.isEnabled = isEnabled + deleteButton.ifLoaded?.isEnabled = isEnabled + } + + var body: Layout { + HStack { + commentLabel + if showDeleteButtonView { /// If false, the view is not loaded. + deleteButton + } + if showReplyButton { /// If false, the view is not loaded. + replyButton + } + } + } +} +``` + + +Performance Wins: + +1. https://fburl.com/workplace/j03wj8h0 + + diff --git a/Sources/QuickLayout/docs/docs/quickstart.mdx b/Sources/QuickLayout/docs/docs/quickstart.mdx new file mode 100644 index 0000000..2a85c35 --- /dev/null +++ b/Sources/QuickLayout/docs/docs/quickstart.mdx @@ -0,0 +1,164 @@ +--- +sidebar_position: 2 +id: quickstart +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Quickstart + +QuickLayout is a declarative layout library built to be used with regular UIViews. It can coexist with manual layout methods, allowing for gradual adoption. + +### Stacks + +At the heart of QuickLayout are Stacks: +- VStack layouts child elements vertically, +- HStack positions child elements horizontally. + +For example, to make the following layout (image below), you can use VStack with leading alignment and spacing of four points: + + + +```swift +let label1 = UILabel(text: "Kylee Lessie") +let label2 = UILabel(text: "Developer") + +var body: Layout { + VStack(alignment: .leading, spacing: 4) { + label1 + label2 + } +} +``` + +You can nest Stacks as many times as you wish. For instance, you can add an icon to the left of two labels by using HStack: + + + +```swift +let avatarView = UIImage(image: avatarIcon) +let label1 = UILabel(text: "Kylee Lessie") +let label2 = UILabel(text: "Developer") + +var body: Layout { + HStack(spacing: 8) { + avatarView + VStack(alignment: .leading, spacing: 4) { + label1 + label2 + } + } +} +``` + +### Alignment in Stacks + +The alignment property in stacks does not position the stack within its parent; instead, it controls the alignment of the child elements inside the stack. + +The alignment property of a VStack only applies to the horizontal alignment of the contained views. Similarly, the alignment property for an HStack only controls the vertical alignment. + + + +### Flexible Spacer + +A flexible Spacer is an adaptive element that expands as much as possible. For example, when placed within an HStack, a spacer expands horizontally as much as the stack allows, moving sibling views out of the way within the stack’s size limits. + + + +```swift +var body: Layout { + HStack { + view1 + Spacer() + } +} +``` + + + +```swift +var body: Layout { + HStack { + view1 + Spacer() + view2 + } +} +``` + + + +```swift +var body: Layout { + HStack { + Spacer() + view1 + } +} +``` + +### Fixed spacer and Spacing + + + +A fixed space between views can be specified with a Spacer as in the snippet below: + + +```swift +var body: Layout { + HStack { + view1 + Spacer(8) + view2 + Spacer(8) + view3 + } +} +``` +You can achieve the same fixed spacing between views using the Stack's spacing parameter: + +```swift +var body: Layout { + HStack(spacing: 8) { + view1 + view2 + view3 + } +} +``` + +### Padding + +A padding can be added to any view or layout element: + +```swift +var body: Layout { + HStack(spacing: 8) { + view1 + view2 + view3 + } + .padding(.horizontal, 16) +} +``` + +### Frame + +The frame does not directly change the size of the target view, but it acts like a "picture frame" by suggesting the child its size and positioning it inside its bounds. For example, if the UIImageView in the following layout has an icon that is 10x10 points in size, the icon will be surrounded by 45 points of empty space on each side. However, if the icon has a size of 90x90 points, it will be surrounded only by 5 points of empty space on each side. + +```swift +var body: Layout { + imageView + .frame(width: 100, height: 100) +} +``` + +To make UIImageView acquire the size of the frame, it needs to be wrapped into resizable modifier: + +```swift +var body: Layout { + imageView + .resizable() + .frame(width: 100, height: 100) +} +``` diff --git a/Sources/QuickLayout/docs/docusaurus.config.js b/Sources/QuickLayout/docs/docusaurus.config.js new file mode 100644 index 0000000..18b46c4 --- /dev/null +++ b/Sources/QuickLayout/docs/docusaurus.config.js @@ -0,0 +1,84 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {fbContent} from 'docusaurus-plugin-internaldocs-fb/internal'; +const lightCodeTheme = require('prism-react-renderer/themes/github'); +const darkCodeTheme = require('prism-react-renderer/themes/dracula'); + +// With JSDoc @type annotations, IDEs can provide config autocompletion +/** @type {import('@docusaurus/types').DocusaurusConfig} */ +(module.exports = { + title: 'QuickLayout', + tagline: 'QuickLayout is a declarative layout library for iOS, designed to work seamlessly with UIKit. It is lightning-fast, incredibly simple to use, and offers a powerful set of modern layout primitives.', + url: fbContent({ + internal: 'https://internalfb.com/', + external: 'https://facebookincubator.github.io/', + }), + baseUrl: fbContent({ + internal: '/', + external: '/QuickLayout/', + }), + onBrokenLinks: 'throw', + trailingSlash: true, + favicon: 'img/favicon.ico', + organizationName: 'facebookincubator', + projectName: 'QuickLayout', + deploymentBranch: 'main', + customFields: { + fbRepoName: 'fbsource', + ossRepoPath: 'fbobjc/Libraries/MobileUI/QuickLayout/docs', + }, + + presets: [ + [ + 'docusaurus-plugin-internaldocs-fb/docusaurus-preset', + /** @type {import('docusaurus-plugin-internaldocs-fb').PresetOptions} */ + ({ + docs: { + breadcrumbs: false, + routeBasePath: '/', // Serve the docs at the site's root + sidebarCollapsible: false, + sidebarPath: require.resolve('./sidebars.js'), + editUrl: fbContent({ + internal: 'https://www.internalfb.com/code/fbsource/fbobjc/Libraries/MobileUI/QuickLayout/docs', + external: 'https://github.com/facebookincubator/QuickLayout/edit/main/Sources/QuickLayout/docs', + }), + }, + experimentalXRepoSnippets: { + baseDir: '.', + }, + staticDocsProject: 'QuickLayout', + trackingFile: 'fbcode/staticdocs/WATCHED_FILES', + blog: false, // Optional: disable the blog plugin + theme: { + customCss: require.resolve('./src/css/custom.css'), + }, + googleAnalytics: { + trackingID: 'G-893KEXXG32', + anonymizeIP: true, + }, + }), + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + navbar: { + title: 'QuickLayout Handbook', + items: [ + ], + }, + footer: { + }, + prism: { + additionalLanguages: ['swift'], + theme: lightCodeTheme, + darkTheme: darkCodeTheme, + }, + }), +}); diff --git a/Sources/QuickLayout/docs/package.json b/Sources/QuickLayout/docs/package.json new file mode 100644 index 0000000..f9a4f10 --- /dev/null +++ b/Sources/QuickLayout/docs/package.json @@ -0,0 +1,51 @@ +{ + "name": "staticdocs-starter", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "clean": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids" + }, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.26.10", + "@babel/preset-env": "^7.26.10", + "@docusaurus/plugin-google-analytics": "^3.9.2", + "@docusaurus/preset-classic": "^3.9.2", + "@mdx-js/react": "^3.1.0", + "classnames": "^2.5.1", + "clsx": "^1.1.1", + "docusaurus-plugin-internaldocs-fb": "1.19.1", + "loader-utils": "3.2.1", + "prism-react-renderer": "^1.3.3", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "engines": { + "node": ">=16", + "npm": "use yarn instead", + "yarn": "^1.5" + }, + "devDependencies": { + "yarn-audit-fix": "^9.3.10" + } +} diff --git a/Sources/QuickLayout/docs/sidebars.js b/Sources/QuickLayout/docs/sidebars.js new file mode 100644 index 0000000..7ba1a06 --- /dev/null +++ b/Sources/QuickLayout/docs/sidebars.js @@ -0,0 +1,85 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ + +module.exports = { + + // But you can create a sidebar manually + docs: [ + 'intro', + 'quickstart', + 'code-samples', + , + { + 'How To Use': [ + 'how-to-use/macro-layout-integration', + 'how-to-use/manual-layout-integration', + ] + }, + { + 'Containers': [ + 'layout/hstack', + 'layout/vstack', + 'layout/zstack', + 'layout/hflow', + 'layout/vflow', + 'layout/grid', + ] + }, + { + 'Modifiers': [ + 'layout/padding', + 'layout/fixed-frame', + 'layout/flexible-frame', + 'layout/resizable', + 'layout/expandable', + 'layout/offset', + 'layout/aspect-ratio', + 'layout/overlay', + 'layout/background', + 'layout/layout-priority', + 'layout/layout-direction', + 'layout/fixed-size', + 'layout/ideal-layout', + 'layout/custom-alignments', + 'layout/alignment-guides', + ] + }, + { + 'Control Flows': [ + 'layout/if-else', + 'layout/for-loops', + 'layout/foreach', + ] + }, + { + 'Miscellaneous': [ + 'layout/empty-layout', + 'lazy-views', + ] + }, + { + 'Concepts': [ + 'concepts/layout-trees', + 'concepts/proposed-size', + 'concepts/flexibilities', + 'concepts/double-measurement', + 'concepts/thread-safety', + ] + } + ], +}; diff --git a/Sources/QuickLayout/docs/src/components/HomepageFeatures.js b/Sources/QuickLayout/docs/src/components/HomepageFeatures.js new file mode 100644 index 0000000..1b56aeb --- /dev/null +++ b/Sources/QuickLayout/docs/src/components/HomepageFeatures.js @@ -0,0 +1,71 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import styles from './HomepageFeatures.module.css'; + +const FeatureList = [ + // { + // title: 'Easy to Use', + // Svg: require('../../static/img/undraw_docusaurus_mountain.svg').default, + // description: ( + // <> + // Docusaurus was designed from the ground up to be easily installed and + // used to get your website up and running QuickLayout. + // + // ), + // }, + // { + // title: 'Focus on What Matters', + // Svg: require('../../static/img/undraw_docusaurus_tree.svg').default, + // description: ( + // <> + // Docusaurus lets you focus on your docs, and we'll do the chores. Go + // ahead and move your docs into the docs directory. + // + // ), + // }, + // { + // title: 'Powered by React', + // Svg: require('../../static/img/undraw_docusaurus_react.svg').default, + // description: ( + // <> + // Extend or customize your website layout by reusing React. Docusaurus can + // be extended while reusing the same header and footer. + // + // ), + // }, +]; + +function Feature({Svg, title, description}) { + return ( +
+
+ +
+
+

{title}

+

{description}

+
+
+ ); +} + +export default function HomepageFeatures() { + return ( +
+
+
+ {FeatureList.map((props, idx) => ( + + ))} +
+
+
+ ); +} diff --git a/Sources/QuickLayout/docs/src/components/HomepageFeatures.module.css b/Sources/QuickLayout/docs/src/components/HomepageFeatures.module.css new file mode 100644 index 0000000..2861728 --- /dev/null +++ b/Sources/QuickLayout/docs/src/components/HomepageFeatures.module.css @@ -0,0 +1,18 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.features { + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; +} + +.featureSvg { + height: 200px; + width: 200px; +} diff --git a/Sources/QuickLayout/docs/src/css/custom.css b/Sources/QuickLayout/docs/src/css/custom.css new file mode 100644 index 0000000..ea6c6c4 --- /dev/null +++ b/Sources/QuickLayout/docs/src/css/custom.css @@ -0,0 +1,343 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ + + +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); + + +/** https://docusaurus.community/knowledge/design/css/variables/ */ +:root { +/** Text */ + --ifm-font-color-base: #1C1E21; + --ifm-font-color-base-inverse: #F5F5F7; + --ifm-font-family-base: 'Roboto'; + +/** Menu */ + --ifm-menu-color: #4B4F56; + + --ifm-color-primary: #3c86ff; + --ifm-color-primary-dark: #1e74ff; + --ifm-color-primary-darker: #0e6aff; + --ifm-color-primary-darkest: #0055e0; + --ifm-color-primary-light: #5a99ff; + --ifm-color-primary-lighter: #6aa3ff; + --ifm-color-primary-lightest: #97bfff; + --ifm-code-font-size: 92%; + --ifm-global-shadow-lw: null; + --ifm-global-radius: 1rem; + --ifm-footer-background-color: #ffffff00; + --ifm-toc-border-color: #ffffff00; + --ifm-menu-color-background-active: #ffffff00; + --docs-color-border: #dadde1; + --ifm-menu-color: #454746; +} + +html[data-theme='dark'] { + --ifm-font-color-base: #F5F5F7; + --ifm-menu-color: #F5F5F7; + --ifm-background-color: #0D1117; + --ifm-code-background: #1E242A; + --ifm-toc-border-color: #ffffff00; + --ifm-menu-color-background-active: #ffffff00; + --ifm-navbar-background-color: #25292E; + + --ifm-alert-color: yellow; + --ifm-alert-border-color: red; + --docs-color-border: null; + pre { + background-color: #151B23; + } +} + +.navbar__title { + font-weight: medium !important; +} + +h1 { font-size: 36px !important; line-height: 40px; } +h2 { font-size: 30px !important; line-height: 36px; } +h3 { font-size: 24px !important; line-height: 32px; } +body { font-size: 16px !important; line-height: 26px; } + +li code,p code,td code { + border-radius: 6px !important; + padding: 2px 3px +} + +.pagination-nav__link--prev .pagination-nav__label { + text-align: right +} + +.pagination-nav__link--next .pagination-nav__label { + text-align: left +} + +.pagination-nav__link { + border-radius: 2rem +} + + +.pagination-nav__sublabel { + display: none; +} + +pre code { + background: #f0f4ff; + border: 1px solid #c6d6ff; +} + +[data-theme=dark] details.alert.alert--info,[data-theme=dark] pre code { + background: #1e2538; + border: 1px solid #2d3956 +} + +/** source https://github.com/facebook/docusaurus/issues/7562#issuecomment-1176697127 */ +@media (min-width: 1416px) { + .main-wrapper { + align-self: center; + max-width: 1400px; + width: 1400px; + } +} + +/* Light Theme */ +.alert--secondary { + background-color: #f5f7fa; + border: 1px solid #e4e7eb; +} + +.alert--success { + background-color: #e6f4ea; + border: 1px solid #c8e6c9; +} + +.alert--success code { + background-color: #d5f4ea; + border: 1px solid #c8e6c9; +} + +.alert--info { + background-color: #e8f1fa; + border: 1px solid #d0e3fa; +} + +.alert--info code { + background-color: #d0e3fa; + border: 1px solid #d0e3fa; + border-radius: 12px; +} + +.alert--warning { + background-color: #fff8e1; + border: 1px solid #ffe082; +} + +.alert--warning code { + background-color: #ffe5b2; + border: 1px solid #ffe082; +} + +.alert--danger { + background-color: #fdecea; + border: 1px solid #ffccd5; +} + +.alert--danger code { + background-color: #ffccd5; + border: 1px solid #ffccd5; +} + +/* Dark Theme */ +[data-theme=dark] .alert--secondary { + background-color: #23272f; + border: 1px solid #262a32; /* barely lighter than background */ +} + +[data-theme=dark] .alert--success { + background-color: #1b3c2e; + border: 1px solid #204635; /* subtle green tint, low contrast */ +} + +[data-theme=dark] .alert--success code { + background-color: #255d3a; + border: 1px solid #255d3a; + color: #e6f4ea; +} + +[data-theme=dark] .alert--info { + background-color: #1a2636; + border: 1px solid #202d40; /* subtle blue-gray */ +} + +[data-theme=dark] .alert--info code { + background-color: #223a5e; + border: 1px solid #223a5e; + color: #e8f1fa; +} + +[data-theme=dark] .alert--warning { + background-color: #3a2e1a; + border: 1px solid #4a3a1e; /* subtle brown/yellow */ +} + +[data-theme=dark] .alert--warning code { + background-color: #5c4320; + border: 1px solid #5c4320; + color: #fff8e1; +} + +[data-theme=dark] .alert--danger { + background-color: #3a2323; + border: 1px solid #4a2a2a; /* subtle red-brown */ +} + +[data-theme=dark] .alert--danger code { + background-color: #5e2a2a; + border: 1px solid #5e2a2a; + color: #fdecea; +} + + /* https://icon-sets.iconify.design/solar/pen-outline/ */ +.alert--success svg { + display: inline-block; + --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M14.757 2.621a4.682 4.682 0 0 1 6.622 6.622l-9.486 9.486c-.542.542-.86.86-1.216 1.137q-.628.492-1.35.835c-.406.193-.834.336-1.56.578l-3.332 1.11l-.802.268a1.81 1.81 0 0 1-2.29-2.29l1.378-4.133c.242-.727.385-1.155.578-1.562q.344-.72.835-1.35c.276-.354.595-.673 1.137-1.215zM4.4 20.821l2.841-.948c.791-.264 1.127-.377 1.44-.526q.572-.274 1.073-.663c.273-.214.525-.463 1.115-1.053l7.57-7.57a7.36 7.36 0 0 1-2.757-1.744A7.36 7.36 0 0 1 13.94 5.56l-7.57 7.57c-.59.589-.84.84-1.053 1.114q-.39.5-.663 1.073c-.149.313-.262.649-.526 1.44L3.18 19.6zM15.155 4.343c.035.175.092.413.189.69a5.86 5.86 0 0 0 1.4 2.222a5.86 5.86 0 0 0 2.221 1.4c.278.097.516.154.691.189l.662-.662a3.182 3.182 0 0 0-4.5-4.5z' clip-rule='evenodd'/%3E%3C/svg%3E"); + background-color: currentColor; + -webkit-mask-image: var(--svg); + mask-image: var(--svg); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; + position: relative; + bottom: 0.1rem; +} + +.alert--secondary svg { + display: inline-block; + --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M14.757 2.621a4.682 4.682 0 0 1 6.622 6.622l-9.486 9.486c-.542.542-.86.86-1.216 1.137q-.628.492-1.35.835c-.406.193-.834.336-1.56.578l-3.332 1.11l-.802.268a1.81 1.81 0 0 1-2.29-2.29l1.378-4.133c.242-.727.385-1.155.578-1.562q.344-.72.835-1.35c.276-.354.595-.673 1.137-1.215zM4.4 20.821l2.841-.948c.791-.264 1.127-.377 1.44-.526q.572-.274 1.073-.663c.273-.214.525-.463 1.115-1.053l7.57-7.57a7.36 7.36 0 0 1-2.757-1.744A7.36 7.36 0 0 1 13.94 5.56l-7.57 7.57c-.59.589-.84.84-1.053 1.114q-.39.5-.663 1.073c-.149.313-.262.649-.526 1.44L3.18 19.6zM15.155 4.343c.035.175.092.413.189.69a5.86 5.86 0 0 0 1.4 2.222a5.86 5.86 0 0 0 2.221 1.4c.278.097.516.154.691.189l.662-.662a3.182 3.182 0 0 0-4.5-4.5z' clip-rule='evenodd'/%3E%3C/svg%3E"); + background-color: currentColor; + -webkit-mask-image: var(--svg); + mask-image: var(--svg); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; + position: relative; + bottom: 0.1rem; +} + +/* https://icon-sets.iconify.design/solar/pen-outline/ */ +.alert--info svg { + display: inline-block; + --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M14.757 2.621a4.682 4.682 0 0 1 6.622 6.622l-9.486 9.486c-.542.542-.86.86-1.216 1.137q-.628.492-1.35.835c-.406.193-.834.336-1.56.578l-3.332 1.11l-.802.268a1.81 1.81 0 0 1-2.29-2.29l1.378-4.133c.242-.727.385-1.155.578-1.562q.344-.72.835-1.35c.276-.354.595-.673 1.137-1.215zM4.4 20.821l2.841-.948c.791-.264 1.127-.377 1.44-.526q.572-.274 1.073-.663c.273-.214.525-.463 1.115-1.053l7.57-7.57a7.36 7.36 0 0 1-2.757-1.744A7.36 7.36 0 0 1 13.94 5.56l-7.57 7.57c-.59.589-.84.84-1.053 1.114q-.39.5-.663 1.073c-.149.313-.262.649-.526 1.44L3.18 19.6zM15.155 4.343c.035.175.092.413.189.69a5.86 5.86 0 0 0 1.4 2.222a5.86 5.86 0 0 0 2.221 1.4c.278.097.516.154.691.189l.662-.662a3.182 3.182 0 0 0-4.5-4.5z' clip-rule='evenodd'/%3E%3C/svg%3E"); + background-color: currentColor; + -webkit-mask-image: var(--svg); + mask-image: var(--svg); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; + position: relative; + bottom: 0.1rem; +} + +/* https://icon-sets.iconify.design/solar/shield-warning-outline/ */ +.alert--warning svg { + display: inline-block; + --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 7.25a.75.75 0 0 1 .75.75v4a.75.75 0 0 1-1.5 0V8a.75.75 0 0 1 .75-.75M12 16a1 1 0 1 0 0-2a1 1 0 0 0 0 2'/%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M8.723 2.051c1.444-.494 2.34-.801 3.277-.801s1.833.307 3.277.801l.727.25c1.481.506 2.625.898 3.443 1.23c.412.167.767.33 1.052.495c.275.16.55.359.737.626c.185.263.281.587.341.9c.063.324.1.713.125 1.16c.048.886.048 2.102.048 3.678v1.601c0 6.101-4.608 9.026-7.348 10.224l-.027.011c-.34.149-.66.288-1.027.382c-.387.1-.799.142-1.348.142c-.55 0-.96-.042-1.348-.142c-.367-.094-.687-.233-1.027-.382l-.027-.011C6.858 21.017 2.25 18.092 2.25 11.99v-1.6c0-1.576 0-2.792.048-3.679c.025-.446.062-.835.125-1.16c.06-.312.156-.636.34-.9c.188-.266.463-.465.738-.625c.285-.165.64-.328 1.052-.495c.818-.332 1.962-.724 3.443-1.23zM12 2.75c-.658 0-1.305.212-2.92.764l-.572.196c-1.513.518-2.616.896-3.39 1.21a7 7 0 0 0-.864.404a2 2 0 0 0-.208.139a.4.4 0 0 0-.055.05a.4.4 0 0 0-.032.074q-.03.082-.063.248a7 7 0 0 0-.1.958c-.046.841-.046 2.015-.046 3.624v1.574c0 5.176 3.87 7.723 6.449 8.849c.371.162.586.254.825.315c.228.059.506.095.976.095s.748-.036.976-.095c.24-.06.454-.153.825-.315c2.58-1.126 6.449-3.674 6.449-8.849v-1.574c0-1.609 0-2.783-.046-3.624a7 7 0 0 0-.1-.958a2 2 0 0 0-.063-.248a.4.4 0 0 0-.032-.074a.4.4 0 0 0-.055-.05a2 2 0 0 0-.208-.14a7 7 0 0 0-.864-.402c-.774-.315-1.877-.693-3.39-1.21l-.573-.197C13.305 2.962 12.658 2.75 12 2.75' clip-rule='evenodd'/%3E%3C/svg%3E"); + background-color: currentColor; + -webkit-mask-image: var(--svg); + mask-image: var(--svg); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; + position: relative; + bottom: 0.1rem; +} + +/* https://icon-sets.iconify.design/solar/fire-minimalistic-outline/ */ +.alert--danger svg { + display: inline-block; + --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.5' d='M12 21c4.418 0 8-3.356 8-7.496c0-3.741-2.035-6.666-3.438-8.06c-.26-.258-.694-.144-.84.189c-.748 1.69-2.304 4.123-4.293 4.123c-1.232.165-3.112-.888-1.594-6.107c.137-.47-.365-.848-.749-.534C6.905 4.905 4 8.511 4 13.504C4 17.644 7.582 21 12 21Z'/%3E%3C/svg%3E"); + background-color: currentColor; + -webkit-mask-image: var(--svg); + mask-image: var(--svg); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; + position: relative; + bottom: 0.1rem; +} + +.navbar .navbar__inner { + margin: 0 auto; + max-width: 1360px; +} + +.navbar { + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05); +} + +.pagination-nav__link--prev { + display: none; +} + +.pagination-nav__link--next .pagination-nav__label:after { + content: ''; +} + +.pagination-nav__link--next .pagination-nav__label { + padding-right: 0.5rem; +} + +.pagination-nav__link--next .pagination-nav__label:before { + content: 'Next: '; + font-weight: bold; + padding-left: 0.5rem; +} + +.pagination-nav__link .pagination-nav__label { + color: var(--ifm-font-color-base); + font-size: 1rem; + font-weight: 500; + line-height: 1.5rem; + position: relative +} + +.pagination-nav { + display: flex; + grid-column: none; +} + +.docusaurus-mt-lg { + margin-top: 1rem !important; +} + +.theme-edit-this-page { + color: var(--ifm-font-color-base); + order: 10; +} + +.docusaurus-highlight-code-line { + background-color: rgba(0, 0, 0, 0.1); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); +} + +html[data-theme='dark'] .docusaurus-highlight-code-line { + background-color: rgba(0, 0, 0, 0.3); +} diff --git a/Sources/QuickLayout/docs/src/pages/_index.js b/Sources/QuickLayout/docs/src/pages/_index.js new file mode 100644 index 0000000..f82808c --- /dev/null +++ b/Sources/QuickLayout/docs/src/pages/_index.js @@ -0,0 +1,47 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import styles from './index.module.css'; +import HomepageFeatures from '../components/HomepageFeatures'; + +function HomepageHeader() { + const {siteConfig} = useDocusaurusContext(); + return ( +
+
+

{siteConfig.title}

+

{siteConfig.tagline}

+
+ + QuickLayout Docs + +
+
+
+ ); +} + +export default function Home() { + const {siteConfig} = useDocusaurusContext(); + return ( + + +
+ +
+
+ ); +} diff --git a/Sources/QuickLayout/docs/src/pages/index.module.css b/Sources/QuickLayout/docs/src/pages/index.module.css new file mode 100644 index 0000000..1bc078a --- /dev/null +++ b/Sources/QuickLayout/docs/src/pages/index.module.css @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.heroBanner { + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; +} + +@media screen and (max-width: 966px) { + .heroBanner { + padding: 2rem; + } +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/Sources/QuickLayout/docs/src/pages/markdown-page.mdx b/Sources/QuickLayout/docs/src/pages/markdown-page.mdx new file mode 100644 index 0000000..9756c5b --- /dev/null +++ b/Sources/QuickLayout/docs/src/pages/markdown-page.mdx @@ -0,0 +1,7 @@ +--- +title: Markdown page example +--- + +# Markdown page example + +You don't need React to write simple standalone pages. diff --git a/Sources/QuickLayout/docs/src/theme/Footer/index.js b/Sources/QuickLayout/docs/src/theme/Footer/index.js new file mode 100644 index 0000000..7ed6364 --- /dev/null +++ b/Sources/QuickLayout/docs/src/theme/Footer/index.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Footer from '@theme-original/Footer'; + +export default function FooterWrapper(props) { + return ( + + ); +} diff --git a/Sources/QuickLayout/docs/static/.nojekyll b/Sources/QuickLayout/docs/static/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_sizeThatFits_example.png b/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_sizeThatFits_example.png new file mode 100644 index 0000000..0949c2f Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_sizeThatFits_example.png differ diff --git a/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_sizeThatFits_example_2.png b/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_sizeThatFits_example_2.png new file mode 100644 index 0000000..3a27b46 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_sizeThatFits_example_2.png differ diff --git a/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_textRectForBounds_example.png b/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_textRectForBounds_example.png new file mode 100644 index 0000000..0949c2f Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/calculate-size-label/UILabel_textRectForBounds_example.png differ diff --git a/Sources/QuickLayout/docs/static/img/concepts/layout-trees1.png b/Sources/QuickLayout/docs/static/img/concepts/layout-trees1.png new file mode 100644 index 0000000..5823581 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/concepts/layout-trees1.png differ diff --git a/Sources/QuickLayout/docs/static/img/concepts/layout-trees2.png b/Sources/QuickLayout/docs/static/img/concepts/layout-trees2.png new file mode 100644 index 0000000..f799ebd Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/concepts/layout-trees2.png differ diff --git a/Sources/QuickLayout/docs/static/img/concepts/layout-trees3.png b/Sources/QuickLayout/docs/static/img/concepts/layout-trees3.png new file mode 100644 index 0000000..598271a Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/concepts/layout-trees3.png differ diff --git a/Sources/QuickLayout/docs/static/img/concepts/layout-trees4.png b/Sources/QuickLayout/docs/static/img/concepts/layout-trees4.png new file mode 100644 index 0000000..2454965 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/concepts/layout-trees4.png differ diff --git a/Sources/QuickLayout/docs/static/img/favicon.ico b/Sources/QuickLayout/docs/static/img/favicon.ico new file mode 100644 index 0000000..b0be21e Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/favicon.ico differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/alignment-guides1.png b/Sources/QuickLayout/docs/static/img/layout-elements/alignment-guides1.png new file mode 100644 index 0000000..cf90bdf Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/alignment-guides1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/control-flow1.png b/Sources/QuickLayout/docs/static/img/layout-elements/control-flow1.png new file mode 100644 index 0000000..0b54f99 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/control-flow1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/custom-alignments1.png b/Sources/QuickLayout/docs/static/img/layout-elements/custom-alignments1.png new file mode 100644 index 0000000..e9c3d0d Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/custom-alignments1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/fixed-size1.png b/Sources/QuickLayout/docs/static/img/layout-elements/fixed-size1.png new file mode 100644 index 0000000..3507f6f Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/fixed-size1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/frame1.png b/Sources/QuickLayout/docs/static/img/layout-elements/frame1.png new file mode 100644 index 0000000..0fee809 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/frame1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/frame2.png b/Sources/QuickLayout/docs/static/img/layout-elements/frame2.png new file mode 100644 index 0000000..fb2db7e Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/frame2.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid1.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid1.png new file mode 100644 index 0000000..2cc500d Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid2.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid2.png new file mode 100644 index 0000000..1467a0e Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid2.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid3.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid3.png new file mode 100644 index 0000000..eac7ef4 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid3.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid4.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid4.png new file mode 100644 index 0000000..dfad2ae Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid4.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid5.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid5.png new file mode 100644 index 0000000..f7d7811 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid5.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid6.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid6.png new file mode 100644 index 0000000..067afb5 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid6.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid7.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid7.png new file mode 100644 index 0000000..db0e863 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid7.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid8.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid8.png new file mode 100644 index 0000000..686b8d5 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid8.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/grid9.png b/Sources/QuickLayout/docs/static/img/layout-elements/grid9.png new file mode 100644 index 0000000..b8b51c0 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/grid9.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow1.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow1.png new file mode 100644 index 0000000..084596c Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow2.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow2.png new file mode 100644 index 0000000..d3941ac Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow2.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow3.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow3.png new file mode 100644 index 0000000..2bb0d71 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow3.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow4.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow4.png new file mode 100644 index 0000000..869d26e Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow4.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow5.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow5.png new file mode 100644 index 0000000..2cbd60f Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow5.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow6.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow6.png new file mode 100644 index 0000000..b214b00 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow6.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow7.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow7.png new file mode 100644 index 0000000..5b851e9 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow7.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hflow8.png b/Sources/QuickLayout/docs/static/img/layout-elements/hflow8.png new file mode 100644 index 0000000..406d5e2 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hflow8.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hstack1.png b/Sources/QuickLayout/docs/static/img/layout-elements/hstack1.png new file mode 100644 index 0000000..e2f4db3 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hstack1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/hstack2.png b/Sources/QuickLayout/docs/static/img/layout-elements/hstack2.png new file mode 100644 index 0000000..fc2db92 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/hstack2.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/ideal-layout1.png b/Sources/QuickLayout/docs/static/img/layout-elements/ideal-layout1.png new file mode 100644 index 0000000..571f4be Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/ideal-layout1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/ideal-layout2.png b/Sources/QuickLayout/docs/static/img/layout-elements/ideal-layout2.png new file mode 100644 index 0000000..923989f Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/ideal-layout2.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/offset1.png b/Sources/QuickLayout/docs/static/img/layout-elements/offset1.png new file mode 100644 index 0000000..dd84b76 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/offset1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/padding1.png b/Sources/QuickLayout/docs/static/img/layout-elements/padding1.png new file mode 100644 index 0000000..661bf5e Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/padding1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/resizable1.png b/Sources/QuickLayout/docs/static/img/layout-elements/resizable1.png new file mode 100644 index 0000000..06784ed Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/resizable1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/resizable2.png b/Sources/QuickLayout/docs/static/img/layout-elements/resizable2.png new file mode 100644 index 0000000..c3e8fa4 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/resizable2.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/vflow1.png b/Sources/QuickLayout/docs/static/img/layout-elements/vflow1.png new file mode 100644 index 0000000..a628ce3 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/vflow1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/vflow2.png b/Sources/QuickLayout/docs/static/img/layout-elements/vflow2.png new file mode 100644 index 0000000..d230997 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/vflow2.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/vflow3.png b/Sources/QuickLayout/docs/static/img/layout-elements/vflow3.png new file mode 100644 index 0000000..38cf19f Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/vflow3.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/vflow4.png b/Sources/QuickLayout/docs/static/img/layout-elements/vflow4.png new file mode 100644 index 0000000..11e0c10 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/vflow4.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/vstack1.png b/Sources/QuickLayout/docs/static/img/layout-elements/vstack1.png new file mode 100644 index 0000000..94e3c75 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/vstack1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/zstack1.png b/Sources/QuickLayout/docs/static/img/layout-elements/zstack1.png new file mode 100644 index 0000000..1c101f8 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/zstack1.png differ diff --git a/Sources/QuickLayout/docs/static/img/layout-elements/zstack2.png b/Sources/QuickLayout/docs/static/img/layout-elements/zstack2.png new file mode 100644 index 0000000..b1a2573 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/layout-elements/zstack2.png differ diff --git a/Sources/QuickLayout/docs/static/img/quickstart/img1.png b/Sources/QuickLayout/docs/static/img/quickstart/img1.png new file mode 100644 index 0000000..96ee326 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/quickstart/img1.png differ diff --git a/Sources/QuickLayout/docs/static/img/quickstart/img2.png b/Sources/QuickLayout/docs/static/img/quickstart/img2.png new file mode 100644 index 0000000..785c439 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/quickstart/img2.png differ diff --git a/Sources/QuickLayout/docs/static/img/quickstart/img3.png b/Sources/QuickLayout/docs/static/img/quickstart/img3.png new file mode 100644 index 0000000..d3194f3 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/quickstart/img3.png differ diff --git a/Sources/QuickLayout/docs/static/img/quickstart/img4.png b/Sources/QuickLayout/docs/static/img/quickstart/img4.png new file mode 100644 index 0000000..4d9dc4e Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/quickstart/img4.png differ diff --git a/Sources/QuickLayout/docs/static/img/quickstart/img5.png b/Sources/QuickLayout/docs/static/img/quickstart/img5.png new file mode 100644 index 0000000..5545a64 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/quickstart/img5.png differ diff --git a/Sources/QuickLayout/docs/static/img/quickstart/img6.png b/Sources/QuickLayout/docs/static/img/quickstart/img6.png new file mode 100644 index 0000000..9f9c7f6 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/quickstart/img6.png differ diff --git a/Sources/QuickLayout/docs/static/img/quickstart/img7.png b/Sources/QuickLayout/docs/static/img/quickstart/img7.png new file mode 100644 index 0000000..d3003bf Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/quickstart/img7.png differ diff --git a/Sources/QuickLayout/docs/static/img/sample_screenshot_1.png b/Sources/QuickLayout/docs/static/img/sample_screenshot_1.png new file mode 100644 index 0000000..cc06b52 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/sample_screenshot_1.png differ diff --git a/Sources/QuickLayout/docs/static/img/sample_screenshot_2.png b/Sources/QuickLayout/docs/static/img/sample_screenshot_2.png new file mode 100644 index 0000000..61a3640 Binary files /dev/null and b/Sources/QuickLayout/docs/static/img/sample_screenshot_2.png differ diff --git a/Sources/QuickLayout/docs/yarn.lock b/Sources/QuickLayout/docs/yarn.lock new file mode 100644 index 0000000..d63bbc2 --- /dev/null +++ b/Sources/QuickLayout/docs/yarn.lock @@ -0,0 +1,13022 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ai-sdk/gateway@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@ai-sdk/gateway/-/gateway-2.0.5.tgz#28e4ecdc782d37bbeb011e9df9a600616909d860" + integrity sha512-5TTDSl0USWY6YGnb4QmJGplFZhk+p9OT7hZevAaER6OGiZ17LB1GypsGYDpNo/MiVMklk8kX4gk6p1/R/EiJ8Q== + dependencies: + "@ai-sdk/provider" "2.0.0" + "@ai-sdk/provider-utils" "3.0.15" + "@vercel/oidc" "3.0.3" + +"@ai-sdk/provider-utils@3.0.15": + version "3.0.15" + resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-3.0.15.tgz#83cb34ab24c89859a46470895a6fb43bc4f15447" + integrity sha512-kOc6Pxb7CsRlNt+sLZKL7/VGQUd7ccl3/tIK+Bqf5/QhHR0Qm3qRBMz1IwU1RmjJEZA73x+KB5cUckbDl2WF7Q== + dependencies: + "@ai-sdk/provider" "2.0.0" + "@standard-schema/spec" "^1.0.0" + eventsource-parser "^3.0.6" + +"@ai-sdk/provider@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ai-sdk/provider/-/provider-2.0.0.tgz#b853c739d523b33675bc74b6c506b2c690bc602b" + integrity sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA== + dependencies: + json-schema "^0.4.0" + +"@ai-sdk/react@^2.0.30": + version "2.0.86" + resolved "https://registry.yarnpkg.com/@ai-sdk/react/-/react-2.0.86.tgz#292c3f21872f0da5578329e57a827bd844d5890c" + integrity sha512-vqxbbMOKMpYFHZy0aYEO4jtDcKaFCHL/rEtTqAIDlH14GT0uusSjN99gkDHHG3EnbyJSQmk9gqtqbd1GDwlRRg== + dependencies: + "@ai-sdk/provider-utils" "3.0.15" + ai "5.0.86" + swr "^2.2.5" + throttleit "2.1.0" + +"@algolia/abtesting@1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.8.0.tgz#7b0d358ba857e3bc1330ff16b575585c6dfa3158" + integrity sha512-Hb4BkGNnvgCj3F9XzqjiFTpA5IGkjOXwGAOV13qtc27l2qNF8X9rzSp1H5hu8XewlC0DzYtQtZZIOYzRZDyuXg== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/autocomplete-core@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz#702df67a08cb3cfe8c33ee1111ef136ec1a9e232" + integrity sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.19.2" + "@algolia/autocomplete-shared" "1.19.2" + +"@algolia/autocomplete-plugin-algolia-insights@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz#3584b625b9317e333d1ae43664d02358e175c52d" + integrity sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg== + dependencies: + "@algolia/autocomplete-shared" "1.19.2" + +"@algolia/autocomplete-shared@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz#c0b7b8dc30a5c65b70501640e62b009535e4578f" + integrity sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w== + +"@algolia/client-abtesting@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.42.0.tgz#c72516e9a266faaf537d367c74dfd66b7a2d774a" + integrity sha512-JLyyG7bb7XOda+w/sp8ch7rEVy6LnWs3qtxr6VJJ2XIINqGsY6U+0L3aJ6QFliBRNUeEAr2QBDxSm8u9Sal5uA== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/client-analytics@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.42.0.tgz#9b7051e4256bb893c622a6d5cafc4d335a677b9d" + integrity sha512-SkCrvtZpdSWjNq9NGu/TtOg4TbzRuUToXlQqV6lLePa2s/WQlEyFw7QYjrz4itprWG9ASuH+StDlq7n49F2sBA== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/client-common@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.42.0.tgz#9187dca4d845faca499f9581a11460ecebe474c5" + integrity sha512-6iiFbm2tRn6B2OqFv9XDTcw5LdWPudiJWIbRk+fsTX+hkPrPm4e1/SbU+lEYBciPoaTShLkDbRge4UePEyCPMQ== + +"@algolia/client-insights@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.42.0.tgz#fb234d81635ef1ed53cd2125f3d48d549ad8a18f" + integrity sha512-iEokmw2k6FBa8g/TT7ClyEriaP/FUEmz3iczRoCklEHWSgoABMkaeYrxRXrA2yx76AN+gyZoC8FX0iCJ55dsOg== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/client-personalization@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.42.0.tgz#0c778db08e2d61f387c511587c6f0bc6d7148a98" + integrity sha512-ivVniRqX2ARd+jGvRHTxpWeOtO9VT+rK+OmiuRgkSunoTyxk0vjeDO7QkU7+lzBOXiYgakNjkZrBtIpW9c+muw== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/client-query-suggestions@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.42.0.tgz#82566746a13a9d4ff15019e630711cfb833f92a2" + integrity sha512-9+BIw6rerUfA+eLMIS2lF4mgoeBGTCIHiqb35PLn3699Rm3CaJXz03hChdwAWcA6SwGw0haYXYJa7LF0xI6EpA== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/client-search@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.42.0.tgz#b4c9823af8621a17f1a9878242ac7518519ef0d8" + integrity sha512-NZR7yyHj2WzK6D5X8gn+/KOxPdzYEXOqVdSaK/biU8QfYUpUuEA0sCWg/XlO05tPVEcJelF/oLrrNY3UjRbOww== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/events@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" + integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== + +"@algolia/ingestion@1.42.0": + version "1.42.0" + resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.42.0.tgz#4c542836fb5644a747d9d0f643685533e1431680" + integrity sha512-MBkjRymf4BT6VOvMpJlg6kq8K+PkH9q+N+K4YMNdzTXlL40YwOa1wIWQ5LxP/Jhlz64kW5g9/oaMWY06Sy9dcw== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/monitoring@1.42.0": + version "1.42.0" + resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.42.0.tgz#8b68d4fe416ae686b280b13400a318d0afda7da3" + integrity sha512-kmLs7YfjT4cpr4FnhhRmnoSX4psh9KYZ9NAiWt/YcUV33m0B/Os5L4QId30zVXkOqAPAEpV5VbDPWep+/aoJdQ== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/recommend@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.42.0.tgz#0576b5428b66a300767d1befd91f79a2c825ec3e" + integrity sha512-U5yZ8+Jj+A4ZC0IMfElpPcddQ9NCoawD1dKyWmjHP49nzN2Z4284IFVMAJWR6fq/0ddGf4OMjjYO9cnF8L+5tw== + dependencies: + "@algolia/client-common" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +"@algolia/requester-browser-xhr@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.42.0.tgz#2de48dbf2c85e20e70c8f9091a5e2ef2a1254707" + integrity sha512-EbuxgteaYBlKgc2Fs3JzoPIKAIaevAIwmv1F+fakaEXeibG4pkmVNsyTUjpOZIgJ1kXeqNvDrcjRb6g3vYBJ9A== + dependencies: + "@algolia/client-common" "5.42.0" + +"@algolia/requester-fetch@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.42.0.tgz#c3db64e0896e60b7ebd4a8e4a151c2199854ff0a" + integrity sha512-4vnFvY5Q8QZL9eDNkywFLsk/eQCRBXCBpE8HWs8iUsFNHYoamiOxAeYMin0W/nszQj6abc+jNxMChHmejO+ftQ== + dependencies: + "@algolia/client-common" "5.42.0" + +"@algolia/requester-node-http@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.42.0.tgz#18347c33d59c8c6f8c145f7cfa62e3c76f4569b8" + integrity sha512-gkLNpU+b1pCIwk1hKTJz2NWQPT8gsfGhQasnZ5QVv4jd79fKRL/1ikd86P0AzuIQs9tbbhlMwxsSTyJmlq502w== + dependencies: + "@algolia/client-common" "5.42.0" + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + +"@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5", "@babel/compat-data@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" + integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== + +"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" + integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== + +"@babel/core@^7.21.3", "@babel/core@^7.25.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" + integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/traverse" "^7.26.10" + "@babel/types" "^7.26.10" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.12.5": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15" + integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ== + dependencies: + "@babel/types" "^7.15.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.25.9", "@babel/generator@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" + integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang== + dependencies: + "@babel/parser" "^7.26.10" + "@babel/types" "^7.26.10" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/generator@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" + integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== + dependencies: + "@babel/parser" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== + dependencies: + "@babel/types" "^7.25.9" + +"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.25.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" + integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/helper-replace-supers" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/traverse" "^7.26.9" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.26.10", "@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" + integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.28.5" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + +"@babel/helper-create-regexp-features-plugin@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0" + integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + regexpu-core "^6.2.0" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.27.1": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" + integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + regexpu-core "^6.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz#f4f2792fae2ef382074bc2d713522cf24e6ddb21" + integrity sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-define-polyfill-provider@^0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz#742ccf1cb003c07b48859fc9fa2c1bbe40e5f753" + integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== + dependencies: + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + debug "^4.4.1" + lodash.debounce "^4.0.8" + resolve "^1.22.10" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-member-expression-to-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" + integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" + integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== + dependencies: + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-optimise-call-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" + integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== + dependencies: + "@babel/types" "^7.25.9" + +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + +"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + +"@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-remap-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" + integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-wrap-function" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-remap-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" + integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-wrap-function" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-replace-supers@^7.25.9", "@babel/helper-replace-supers@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" + integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/traverse" "^7.26.5" + +"@babel/helper-replace-supers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" + integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" + integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helper-wrap-function@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" + integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g== + dependencies: + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-wrap-function@^7.27.1": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz#fe4872092bc1438ffd0ce579e6f699609f9d0a7a" + integrity sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g== + dependencies: + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.3" + "@babel/types" "^7.28.2" + +"@babel/helpers@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" + integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== + dependencies: + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.10" + +"@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.12.7": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" + integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== + +"@babel/parser@^7.26.10", "@babel/parser@^7.26.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" + integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== + dependencies: + "@babel/types" "^7.26.10" + +"@babel/parser@^7.27.2", "@babel/parser@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" + integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== + dependencies: + "@babel/types" "^7.28.5" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" + integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz#fbde57974707bbfa0376d34d425ff4fa6c732421" + integrity sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.5" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30" + integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz#43f70a6d7efd52370eefbdf55ae03d91b293856d" + integrity sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137" + integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz#beb623bd573b8b6f3047bd04c32506adc3e58a72" + integrity sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1" + integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz#e134a5479eb2ba9c02714e8c1ebf1ec9076124fd" + integrity sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.27.1" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e" + integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz#373f6e2de0016f73caf8f27004f61d167743742a" + integrity sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-import-assertions@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f" + integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-assertions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" + integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-attributes@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-attributes@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" + integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-arrow-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz#6e2061067ba3ab0266d834a9f94811196f2aba9a" + integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-async-generator-functions@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz#5e3991135e3b9c6eaaf5eff56d1ae5a11df45ff8" + integrity sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-remap-async-to-generator" "^7.25.9" + "@babel/traverse" "^7.26.8" + +"@babel/plugin-transform-async-generator-functions@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" + integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/traverse" "^7.28.0" + +"@babel/plugin-transform-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71" + integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-remap-async-to-generator" "^7.25.9" + +"@babel/plugin-transform-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" + integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" + +"@babel/plugin-transform-block-scoped-functions@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz#3dc4405d31ad1cbe45293aa57205a6e3b009d53e" + integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-block-scoped-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz#558a9d6e24cf72802dd3b62a4b51e0d62c0f57f9" + integrity sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-block-scoping@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1" + integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-block-scoping@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz#e0d3af63bd8c80de2e567e690a54e84d85eb16f6" + integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-class-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f" + integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-class-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" + integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-class-static-block@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0" + integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-class-static-block@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz#d1b8e69b54c9993bc558203e1f49bfc979bfd852" + integrity sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.3" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-classes@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52" + integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/traverse" "^7.25.9" + globals "^11.1.0" + +"@babel/plugin-transform-classes@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" + integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-globals" "^7.28.0" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/traverse" "^7.28.4" + +"@babel/plugin-transform-computed-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b" + integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/template" "^7.25.9" + +"@babel/plugin-transform-computed-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" + integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/template" "^7.27.1" + +"@babel/plugin-transform-destructuring@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1" + integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" + integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.5" + +"@babel/plugin-transform-dotall-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a" + integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-dotall-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d" + integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-duplicate-keys@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d" + integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-duplicate-keys@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz#f1fbf628ece18e12e7b32b175940e68358f546d1" + integrity sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31" + integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec" + integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-dynamic-import@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8" + integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-dynamic-import@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz#4c78f35552ac0e06aa1f6e3c573d67695e8af5a4" + integrity sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-explicit-resource-management@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz#45be6211b778dbf4b9d54c4e8a2b42fa72e09a1a" + integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.28.0" + +"@babel/plugin-transform-exponentiation-operator@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc" + integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-exponentiation-operator@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz#7cc90a8170e83532676cfa505278e147056e94fe" + integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-export-namespace-from@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2" + integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-export-namespace-from@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz#71ca69d3471edd6daa711cf4dfc3400415df9c23" + integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-for-of@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz#27231f79d5170ef33b5111f07fe5cafeb2c96a56" + integrity sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + +"@babel/plugin-transform-for-of@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz#bc24f7080e9ff721b63a70ac7b2564ca15b6c40a" + integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-function-name@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97" + integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-transform-function-name@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz#4d0bf307720e4dce6d7c30fcb1fd6ca77bdeb3a7" + integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== + dependencies: + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-transform-json-strings@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660" + integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-json-strings@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c" + integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de" + integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz#baaefa4d10a1d4206f9dcdda50d7d5827bb70b24" + integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-logical-assignment-operators@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7" + integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-logical-assignment-operators@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz#d028fd6db8c081dee4abebc812c2325e24a85b0e" + integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-member-expression-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de" + integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-member-expression-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz#37b88ba594d852418e99536f5612f795f23aeaf9" + integrity sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-amd@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5" + integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-modules-amd@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz#a4145f9d87c2291fe2d05f994b65dba4e3e7196f" + integrity sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-commonjs@^7.25.9", "@babel/plugin-transform-modules-commonjs@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" + integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== + dependencies: + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-modules-commonjs@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832" + integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-systemjs@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8" + integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-transform-modules-systemjs@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" + integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== + dependencies: + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.5" + +"@babel/plugin-transform-modules-umd@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9" + integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-modules-umd@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz#63f2cf4f6dc15debc12f694e44714863d34cd334" + integrity sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a" + integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" + integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-new-target@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd" + integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-new-target@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz#259c43939728cad1706ac17351b7e6a7bea1abeb" + integrity sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.26.6": + version "7.26.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz#fbf6b3c92cb509e7b319ee46e3da89c5bedd31fe" + integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" + integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-numeric-separator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1" + integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-numeric-separator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" + integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-object-rest-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18" + integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + +"@babel/plugin-transform-object-rest-spread@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" + integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== + dependencies: + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/traverse" "^7.28.4" + +"@babel/plugin-transform-object-super@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03" + integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + +"@babel/plugin-transform-object-super@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz#1c932cd27bf3874c43a5cac4f43ebf970c9871b5" + integrity sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + +"@babel/plugin-transform-optional-catch-binding@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3" + integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-optional-catch-binding@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" + integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" + integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + +"@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz#8238c785f9d5c1c515a90bf196efb50d075a4b26" + integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-parameters@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257" + integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-parameters@^7.27.7": + version "7.27.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz#1fd2febb7c74e7d21cf3b05f7aebc907940af53a" + integrity sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-methods@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" + integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-private-methods@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" + integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-property-in-object@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33" + integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-private-property-in-object@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" + integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-property-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f" + integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-property-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz#07eafd618800591e88073a0af1b940d9a42c6424" + integrity sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-constant-elements@^7.21.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz#08a1de35a301929b60fdf2788a54b46cd8ecd0ef" + integrity sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-display-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-display-name@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz#4b79746b59efa1f38c8695065a92a9f5afb24f7d" + integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.18.6" + +"@babel/plugin-transform-react-jsx-development@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz#8fd220a77dd139c07e25225a903b8be8c829e0d7" + integrity sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.25.9" + +"@babel/plugin-transform-react-jsx@^7.18.6": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.19.0" + +"@babel/plugin-transform-react-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166" + integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/plugin-transform-react-pure-annotations@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" + integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-pure-annotations@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz#ea1c11b2f9dbb8e2d97025f43a3b5bc47e18ae62" + integrity sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-regenerator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" + integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-regenerator@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" + integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-regexp-modifiers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850" + integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-regexp-modifiers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09" + integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-reserved-words@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce" + integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-reserved-words@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz#40fba4878ccbd1c56605a4479a3a891ac0274bb4" + integrity sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-runtime@^7.25.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz#6b4504233de8238e7d666c15cde681dc62adff87" + integrity sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.26.5" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.11.0" + babel-plugin-polyfill-regenerator "^0.6.1" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" + integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-shorthand-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90" + integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9" + integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + +"@babel/plugin-transform-spread@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" + integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-sticky-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32" + integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-sticky-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280" + integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-template-literals@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz#966b15d153a991172a540a69ad5e1845ced990b5" + integrity sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-template-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz#1a0eb35d8bb3e6efc06c9fd40eb0bcef548328b8" + integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-typeof-symbol@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz#d0e33acd9223744c1e857dbd6fa17bd0a3786937" + integrity sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-typeof-symbol@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz#70e966bb492e03509cf37eafa6dcc3051f844369" + integrity sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-typescript@^7.25.9": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz#2e9caa870aa102f50d7125240d9dbf91334b0950" + integrity sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-syntax-typescript" "^7.25.9" + +"@babel/plugin-transform-unicode-escapes@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82" + integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-unicode-escapes@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz#3e3143f8438aef842de28816ece58780190cf806" + integrity sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-property-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3" + integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-unicode-property-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956" + integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1" + integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-unicode-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz#25948f5c395db15f609028e370667ed8bae9af97" + integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-sets-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe" + integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-unicode-sets-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1" + integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/preset-env@^7.20.2", "@babel/preset-env@^7.25.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.9.tgz#2ec64e903d0efe743699f77a10bdf7955c2123c3" + integrity sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ== + dependencies: + "@babel/compat-data" "^7.26.8" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions" "^7.26.0" + "@babel/plugin-syntax-import-attributes" "^7.26.0" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.25.9" + "@babel/plugin-transform-async-generator-functions" "^7.26.8" + "@babel/plugin-transform-async-to-generator" "^7.25.9" + "@babel/plugin-transform-block-scoped-functions" "^7.26.5" + "@babel/plugin-transform-block-scoping" "^7.25.9" + "@babel/plugin-transform-class-properties" "^7.25.9" + "@babel/plugin-transform-class-static-block" "^7.26.0" + "@babel/plugin-transform-classes" "^7.25.9" + "@babel/plugin-transform-computed-properties" "^7.25.9" + "@babel/plugin-transform-destructuring" "^7.25.9" + "@babel/plugin-transform-dotall-regex" "^7.25.9" + "@babel/plugin-transform-duplicate-keys" "^7.25.9" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-dynamic-import" "^7.25.9" + "@babel/plugin-transform-exponentiation-operator" "^7.26.3" + "@babel/plugin-transform-export-namespace-from" "^7.25.9" + "@babel/plugin-transform-for-of" "^7.26.9" + "@babel/plugin-transform-function-name" "^7.25.9" + "@babel/plugin-transform-json-strings" "^7.25.9" + "@babel/plugin-transform-literals" "^7.25.9" + "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" + "@babel/plugin-transform-member-expression-literals" "^7.25.9" + "@babel/plugin-transform-modules-amd" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.26.3" + "@babel/plugin-transform-modules-systemjs" "^7.25.9" + "@babel/plugin-transform-modules-umd" "^7.25.9" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-new-target" "^7.25.9" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.26.6" + "@babel/plugin-transform-numeric-separator" "^7.25.9" + "@babel/plugin-transform-object-rest-spread" "^7.25.9" + "@babel/plugin-transform-object-super" "^7.25.9" + "@babel/plugin-transform-optional-catch-binding" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + "@babel/plugin-transform-private-methods" "^7.25.9" + "@babel/plugin-transform-private-property-in-object" "^7.25.9" + "@babel/plugin-transform-property-literals" "^7.25.9" + "@babel/plugin-transform-regenerator" "^7.25.9" + "@babel/plugin-transform-regexp-modifiers" "^7.26.0" + "@babel/plugin-transform-reserved-words" "^7.25.9" + "@babel/plugin-transform-shorthand-properties" "^7.25.9" + "@babel/plugin-transform-spread" "^7.25.9" + "@babel/plugin-transform-sticky-regex" "^7.25.9" + "@babel/plugin-transform-template-literals" "^7.26.8" + "@babel/plugin-transform-typeof-symbol" "^7.26.7" + "@babel/plugin-transform-unicode-escapes" "^7.25.9" + "@babel/plugin-transform-unicode-property-regex" "^7.25.9" + "@babel/plugin-transform-unicode-regex" "^7.25.9" + "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.11.0" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.40.0" + semver "^6.3.1" + +"@babel/preset-env@^7.26.10": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.5.tgz#82dd159d1563f219a1ce94324b3071eb89e280b0" + integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== + dependencies: + "@babel/compat-data" "^7.28.5" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.3" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions" "^7.27.1" + "@babel/plugin-syntax-import-attributes" "^7.27.1" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.28.0" + "@babel/plugin-transform-async-to-generator" "^7.27.1" + "@babel/plugin-transform-block-scoped-functions" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.28.5" + "@babel/plugin-transform-class-properties" "^7.27.1" + "@babel/plugin-transform-class-static-block" "^7.28.3" + "@babel/plugin-transform-classes" "^7.28.4" + "@babel/plugin-transform-computed-properties" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.28.5" + "@babel/plugin-transform-dotall-regex" "^7.27.1" + "@babel/plugin-transform-duplicate-keys" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-dynamic-import" "^7.27.1" + "@babel/plugin-transform-explicit-resource-management" "^7.28.0" + "@babel/plugin-transform-exponentiation-operator" "^7.28.5" + "@babel/plugin-transform-export-namespace-from" "^7.27.1" + "@babel/plugin-transform-for-of" "^7.27.1" + "@babel/plugin-transform-function-name" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.27.1" + "@babel/plugin-transform-literals" "^7.27.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" + "@babel/plugin-transform-member-expression-literals" "^7.27.1" + "@babel/plugin-transform-modules-amd" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-modules-systemjs" "^7.28.5" + "@babel/plugin-transform-modules-umd" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-new-target" "^7.27.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" + "@babel/plugin-transform-numeric-separator" "^7.27.1" + "@babel/plugin-transform-object-rest-spread" "^7.28.4" + "@babel/plugin-transform-object-super" "^7.27.1" + "@babel/plugin-transform-optional-catch-binding" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.28.5" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/plugin-transform-private-methods" "^7.27.1" + "@babel/plugin-transform-private-property-in-object" "^7.27.1" + "@babel/plugin-transform-property-literals" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.28.4" + "@babel/plugin-transform-regexp-modifiers" "^7.27.1" + "@babel/plugin-transform-reserved-words" "^7.27.1" + "@babel/plugin-transform-shorthand-properties" "^7.27.1" + "@babel/plugin-transform-spread" "^7.27.1" + "@babel/plugin-transform-sticky-regex" "^7.27.1" + "@babel/plugin-transform-template-literals" "^7.27.1" + "@babel/plugin-transform-typeof-symbol" "^7.27.1" + "@babel/plugin-transform-unicode-escapes" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.27.1" + "@babel/plugin-transform-unicode-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.14" + babel-plugin-polyfill-corejs3 "^0.13.0" + babel-plugin-polyfill-regenerator "^0.6.5" + core-js-compat "^3.43.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" + integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-react-display-name" "^7.18.6" + "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-pure-annotations" "^7.18.6" + +"@babel/preset-react@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa" + integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-transform-react-display-name" "^7.25.9" + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/plugin-transform-react-jsx-development" "^7.25.9" + "@babel/plugin-transform-react-pure-annotations" "^7.25.9" + +"@babel/preset-typescript@^7.21.0", "@babel/preset-typescript@^7.25.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" + integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.25.9" + "@babel/plugin-transform-typescript" "^7.25.9" + +"@babel/runtime-corejs3@^7.25.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.26.10.tgz#5a3185ca2813f8de8ae68622572086edf5cf51f2" + integrity sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg== + dependencies: + core-js-pure "^3.30.2" + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.8.4": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.12.13": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.12.5": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" + integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.25.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" + integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.25.9", "@babel/template@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" + integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/parser" "^7.26.9" + "@babel/types" "^7.26.9" + +"@babel/template@^7.27.1", "@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380" + integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.10" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" + integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.5" + debug "^4.3.1" + +"@babel/types@^7.15.0", "@babel/types@^7.4.4": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" + integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== + dependencies: + "@babel/helper-validator-identifier" "^7.14.9" + to-fast-properties "^2.0.0" + +"@babel/types@^7.18.6", "@babel/types@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@babel/types@^7.21.3", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" + integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" + integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@braintree/sanitize-url@^6.0.1": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" + integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@csstools/cascade-layer-name-parser@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz#43f962bebead0052a9fed1a2deeb11f85efcbc72" + integrity sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A== + +"@csstools/color-helpers@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz#106c54c808cabfd1ab4c602d8505ee584c2996ef" + integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== + +"@csstools/css-calc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.4.tgz#8473f63e2fcd6e459838dd412401d5948f224c65" + integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== + +"@csstools/css-color-parser@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz#4e386af3a99dd36c46fef013cfe4c1c341eed6f0" + integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== + dependencies: + "@csstools/color-helpers" "^5.1.0" + "@csstools/css-calc" "^2.1.4" + +"@csstools/css-parser-algorithms@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" + integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== + +"@csstools/css-tokenizer@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" + integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== + +"@csstools/media-query-list-parser@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz#7aec77bcb89c2da80ef207e73f474ef9e1b3cdf1" + integrity sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ== + +"@csstools/postcss-alpha-function@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz#7989605711de7831bc7cd75b94c9b5bac9c3728e" + integrity sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-cascade-layers@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz#dd2c70db3867b88975f2922da3bfbae7d7a2cae7" + integrity sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + +"@csstools/postcss-color-function-display-p3-linear@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz#3017ff5e1f65307d6083e58e93d76724fb1ebf9f" + integrity sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-color-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz#a7c85a98c77b522a194a1bbb00dd207f40c7a771" + integrity sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-color-mix-function@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz#2f1ee9f8208077af069545c9bd79bb9733382c2a" + integrity sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-color-mix-variadic-function-arguments@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz#b4012b62a4eaa24d694172bb7137f9d2319cb8f2" + integrity sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-content-alt-text@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz#1d52da1762893c32999ff76839e48d6ec7c7a4cb" + integrity sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-contrast-color-function@^2.0.12": + version "2.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz#ca46986d095c60f208d9e3f24704d199c9172637" + integrity sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-exponential-functions@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz#fc03d1272888cb77e64cc1a7d8a33016e4f05c69" + integrity sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-font-format-keywords@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz#6730836eb0153ff4f3840416cc2322f129c086e6" + integrity sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-gamut-mapping@^2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz#be0e34c9f0142852cccfc02b917511f0d677db8b" + integrity sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-gradients-interpolation-method@^5.0.12": + version "5.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz#0955cce4d97203b861bf66742bbec611b2f3661c" + integrity sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-hwb-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz#07f7ecb08c50e094673bd20eaf7757db0162beee" + integrity sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-ic-unit@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz#2ee2da0690db7edfbc469279711b9e69495659d2" + integrity sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-initial@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz#c385bd9d8ad31ad159edd7992069e97ceea4d09a" + integrity sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg== + +"@csstools/postcss-is-pseudo-class@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz#d34e850bcad4013c2ed7abe948bfa0448aa8eb74" + integrity sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + +"@csstools/postcss-light-dark-function@^2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz#0df448aab9a33cb9a085264ff1f396fb80c4437d" + integrity sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-logical-float-and-clear@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz#62617564182cf86ab5d4e7485433ad91e4c58571" + integrity sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ== + +"@csstools/postcss-logical-overflow@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz#c6de7c5f04e3d4233731a847f6c62819bcbcfa1d" + integrity sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA== + +"@csstools/postcss-logical-overscroll-behavior@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz#43c03eaecdf34055ef53bfab691db6dc97a53d37" + integrity sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w== + +"@csstools/postcss-logical-resize@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz#4df0eeb1a61d7bd85395e56a5cce350b5dbfdca6" + integrity sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-logical-viewport-units@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz#016d98a8b7b5f969e58eb8413447eb801add16fc" + integrity sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ== + dependencies: + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-media-minmax@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz#184252d5b93155ae526689328af6bdf3fc113987" + integrity sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" + +"@csstools/postcss-media-queries-aspect-ratio-number-values@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz#f485c31ec13d6b0fb5c528a3474334a40eff5f11" + integrity sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" + +"@csstools/postcss-nested-calc@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz#754e10edc6958d664c11cde917f44ba144141c62" + integrity sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-normalize-display-values@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz#ecdde2daf4e192e5da0c6fd933b6d8aff32f2a36" + integrity sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-oklab-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz#416640ef10227eea1375b47b72d141495950971d" + integrity sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-progressive-custom-properties@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz#c39780b9ff0d554efb842b6bd75276aa6f1705db" + integrity sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-random-function@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz#3191f32fe72936e361dadf7dbfb55a0209e2691e" + integrity sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-relative-color-syntax@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz#ced792450102441f7c160e1d106f33e4b44181f8" + integrity sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-scope-pseudo-class@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz#9fe60e9d6d91d58fb5fc6c768a40f6e47e89a235" + integrity sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q== + dependencies: + postcss-selector-parser "^7.0.0" + +"@csstools/postcss-sign-functions@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz#a9ac56954014ae4c513475b3f1b3e3424a1e0c12" + integrity sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-stepped-value-functions@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz#36036f1a0e5e5ee2308e72f3c9cb433567c387b9" + integrity sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-text-decoration-shorthand@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz#fae1b70f07d1b7beb4c841c86d69e41ecc6f743c" + integrity sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA== + dependencies: + "@csstools/color-helpers" "^5.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-trigonometric-functions@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz#3f94ed2e319b57f2c59720b64e4d0a8a6fb8c3b2" + integrity sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + +"@csstools/postcss-unset-value@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz#7caa981a34196d06a737754864baf77d64de4bba" + integrity sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA== + +"@csstools/selector-resolve-nested@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz#848c6f44cb65e3733e478319b9342b7aa436fac7" + integrity sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g== + +"@csstools/selector-specificity@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz#037817b574262134cabd68fc4ec1a454f168407b" + integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw== + +"@csstools/utilities@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/utilities/-/utilities-2.0.0.tgz#f7ff0fee38c9ffb5646d47b6906e0bc8868bde60" + integrity sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ== + +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@docsearch/css@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-4.2.0.tgz#473bb4c51f4b2b037a71f423e569907ab19e6d72" + integrity sha512-65KU9Fw5fGsPPPlgIghonMcndyx1bszzrDQYLfierN+Ha29yotMHzVS94bPkZS6On9LS8dE4qmW4P/fGjtCf/g== + +"@docsearch/react@^3.9.0 || ^4.1.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-4.2.0.tgz#9dac48dfb4c1e5f18cf7323d8221d99c0d5f3e4e" + integrity sha512-zSN/KblmtBcerf7Z87yuKIHZQmxuXvYc6/m0+qnjyNu+Ir67AVOagTa1zBqcxkVUVkmBqUExdcyrdo9hbGbqTw== + dependencies: + "@ai-sdk/react" "^2.0.30" + "@algolia/autocomplete-core" "1.19.2" + "@docsearch/css" "4.2.0" + ai "^5.0.30" + algoliasearch "^5.28.0" + marked "^16.3.0" + zod "^4.1.8" + +"@docusaurus/babel@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.9.2.tgz#f956c638baeccf2040e482c71a742bc7e35fdb22" + integrity sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA== + dependencies: + "@babel/core" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-runtime" "^7.25.9" + "@babel/preset-env" "^7.25.9" + "@babel/preset-react" "^7.25.9" + "@babel/preset-typescript" "^7.25.9" + "@babel/runtime" "^7.25.9" + "@babel/runtime-corejs3" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@docusaurus/logger" "3.9.2" + "@docusaurus/utils" "3.9.2" + babel-plugin-dynamic-import-node "^2.3.3" + fs-extra "^11.1.1" + tslib "^2.6.0" + +"@docusaurus/bundler@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.9.2.tgz#0ca82cda4acf13a493e3f66061aea351e9d356cf" + integrity sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA== + dependencies: + "@babel/core" "^7.25.9" + "@docusaurus/babel" "3.9.2" + "@docusaurus/cssnano-preset" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + babel-loader "^9.2.1" + clean-css "^5.3.3" + copy-webpack-plugin "^11.0.0" + css-loader "^6.11.0" + css-minimizer-webpack-plugin "^5.0.1" + cssnano "^6.1.2" + file-loader "^6.2.0" + html-minifier-terser "^7.2.0" + mini-css-extract-plugin "^2.9.2" + null-loader "^4.0.1" + postcss "^8.5.4" + postcss-loader "^7.3.4" + postcss-preset-env "^10.2.1" + terser-webpack-plugin "^5.3.9" + tslib "^2.6.0" + url-loader "^4.1.1" + webpack "^5.95.0" + webpackbar "^6.0.1" + +"@docusaurus/core@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.9.2.tgz#cc970f29b85a8926d63c84f8cffdcda43ed266ff" + integrity sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw== + dependencies: + "@docusaurus/babel" "3.9.2" + "@docusaurus/bundler" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + boxen "^6.2.1" + chalk "^4.1.2" + chokidar "^3.5.3" + cli-table3 "^0.6.3" + combine-promises "^1.1.0" + commander "^5.1.0" + core-js "^3.31.1" + detect-port "^1.5.1" + escape-html "^1.0.3" + eta "^2.2.0" + eval "^0.1.8" + execa "5.1.1" + fs-extra "^11.1.1" + html-tags "^3.3.1" + html-webpack-plugin "^5.6.0" + leven "^3.1.0" + lodash "^4.17.21" + open "^8.4.0" + p-map "^4.0.0" + prompts "^2.4.2" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" + react-loadable "npm:@docusaurus/react-loadable@6.0.0" + react-loadable-ssr-addon-v5-slorber "^1.0.1" + react-router "^5.3.4" + react-router-config "^5.1.1" + react-router-dom "^5.3.4" + semver "^7.5.4" + serve-handler "^6.1.6" + tinypool "^1.0.2" + tslib "^2.6.0" + update-notifier "^6.0.2" + webpack "^5.95.0" + webpack-bundle-analyzer "^4.10.2" + webpack-dev-server "^5.2.2" + webpack-merge "^6.0.1" + +"@docusaurus/cssnano-preset@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz#523aab65349db3c51a77f2489048d28527759428" + integrity sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ== + dependencies: + cssnano-preset-advanced "^6.1.2" + postcss "^8.5.4" + postcss-sort-media-queries "^5.2.0" + tslib "^2.6.0" + +"@docusaurus/logger@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.9.2.tgz#6ec6364b90f5a618a438cc9fd01ac7376869f92a" + integrity sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA== + dependencies: + chalk "^4.1.2" + tslib "^2.6.0" + +"@docusaurus/mdx-loader@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz#78d238de6c6203fa811cc2a7e90b9b79e111408c" + integrity sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ== + dependencies: + "@docusaurus/logger" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + "@mdx-js/mdx" "^3.0.0" + "@slorber/remark-comment" "^1.0.0" + escape-html "^1.0.3" + estree-util-value-to-estree "^3.0.1" + file-loader "^6.2.0" + fs-extra "^11.1.1" + image-size "^2.0.2" + mdast-util-mdx "^3.0.0" + mdast-util-to-string "^4.0.0" + rehype-raw "^7.0.0" + remark-directive "^3.0.0" + remark-emoji "^4.0.0" + remark-frontmatter "^5.0.0" + remark-gfm "^4.0.0" + stringify-object "^3.3.0" + tslib "^2.6.0" + unified "^11.0.3" + unist-util-visit "^5.0.0" + url-loader "^4.1.1" + vfile "^6.0.1" + webpack "^5.88.1" + +"@docusaurus/module-type-aliases@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz#993c7cb0114363dea5ef6855e989b3ad4b843a34" + integrity sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew== + dependencies: + "@docusaurus/types" "3.9.2" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + "@types/react-router-dom" "*" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" + react-loadable "npm:@docusaurus/react-loadable@6.0.0" + +"@docusaurus/plugin-content-blog@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz#d5ce51eb7757bdab0515e2dd26a793ed4e119df9" + integrity sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + cheerio "1.0.0-rc.12" + feed "^4.2.2" + fs-extra "^11.1.1" + lodash "^4.17.21" + schema-dts "^1.1.2" + srcset "^4.0.0" + tslib "^2.6.0" + unist-util-visit "^5.0.0" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@docusaurus/plugin-content-docs@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz#cd8f2d1c06e53c3fa3d24bdfcb48d237bf2d6b2e" + integrity sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/module-type-aliases" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + "@types/react-router-config" "^5.0.7" + combine-promises "^1.1.0" + fs-extra "^11.1.1" + js-yaml "^4.1.0" + lodash "^4.17.21" + schema-dts "^1.1.2" + tslib "^2.6.0" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@docusaurus/plugin-content-pages@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz#22db6c88ade91cec0a9e87a00b8089898051b08d" + integrity sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + fs-extra "^11.1.1" + tslib "^2.6.0" + webpack "^5.88.1" + +"@docusaurus/plugin-css-cascade-layers@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz#358c85f63f1c6a11f611f1b8889d9435c11b22f8" + integrity sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + tslib "^2.6.0" + +"@docusaurus/plugin-debug@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz#b5df4db115583f5404a252dbf66f379ff933e53c" + integrity sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + fs-extra "^11.1.1" + react-json-view-lite "^2.3.0" + tslib "^2.6.0" + +"@docusaurus/plugin-google-analytics@3.9.2", "@docusaurus/plugin-google-analytics@^3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz#857fe075fdeccdf6959e62954d9efe39769fa247" + integrity sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + tslib "^2.6.0" + +"@docusaurus/plugin-google-gtag@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz#df75b1a90ae9266b0471909ba0265f46d5dcae62" + integrity sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + "@types/gtag.js" "^0.0.12" + tslib "^2.6.0" + +"@docusaurus/plugin-google-tag-manager@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz#d1a3cf935acb7d31b84685e92d70a1d342946677" + integrity sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + tslib "^2.6.0" + +"@docusaurus/plugin-sitemap@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz#e1d9f7012942562cc0c6543d3cb2cdc4ae713dc4" + integrity sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + fs-extra "^11.1.1" + sitemap "^7.1.1" + tslib "^2.6.0" + +"@docusaurus/plugin-svgr@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz#62857ed79d97c0150d25f7e7380fdee65671163a" + integrity sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + "@svgr/core" "8.1.0" + "@svgr/webpack" "^8.1.0" + tslib "^2.6.0" + webpack "^5.88.1" + +"@docusaurus/preset-classic@^3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz#85cc4f91baf177f8146c9ce896dfa1f0fd377050" + integrity sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/plugin-content-blog" "3.9.2" + "@docusaurus/plugin-content-docs" "3.9.2" + "@docusaurus/plugin-content-pages" "3.9.2" + "@docusaurus/plugin-css-cascade-layers" "3.9.2" + "@docusaurus/plugin-debug" "3.9.2" + "@docusaurus/plugin-google-analytics" "3.9.2" + "@docusaurus/plugin-google-gtag" "3.9.2" + "@docusaurus/plugin-google-tag-manager" "3.9.2" + "@docusaurus/plugin-sitemap" "3.9.2" + "@docusaurus/plugin-svgr" "3.9.2" + "@docusaurus/theme-classic" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/theme-search-algolia" "3.9.2" + "@docusaurus/types" "3.9.2" + +"@docusaurus/theme-classic@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz#6e514f99a0ff42b80afcf42d5e5d042618311ce0" + integrity sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/module-type-aliases" "3.9.2" + "@docusaurus/plugin-content-blog" "3.9.2" + "@docusaurus/plugin-content-docs" "3.9.2" + "@docusaurus/plugin-content-pages" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/theme-translations" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + "@mdx-js/react" "^3.0.0" + clsx "^2.0.0" + infima "0.2.0-alpha.45" + lodash "^4.17.21" + nprogress "^0.2.0" + postcss "^8.5.4" + prism-react-renderer "^2.3.0" + prismjs "^1.29.0" + react-router-dom "^5.3.4" + rtlcss "^4.1.0" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-common@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.9.2.tgz#487172c6fef9815c2746ef62a71e4f5b326f9ba5" + integrity sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag== + dependencies: + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/module-type-aliases" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + clsx "^2.0.0" + parse-numeric-range "^1.3.0" + prism-react-renderer "^2.3.0" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-search-algolia@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz#420fd5b27fc1673b48151fdc9fe7167ba135ed50" + integrity sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw== + dependencies: + "@docsearch/react" "^3.9.0 || ^4.1.0" + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/plugin-content-docs" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/theme-translations" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + algoliasearch "^5.37.0" + algoliasearch-helper "^3.26.0" + clsx "^2.0.0" + eta "^2.2.0" + fs-extra "^11.1.1" + lodash "^4.17.21" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-translations@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz#238cd69c2da92d612be3d3b4f95944c1d0f1e041" + integrity sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA== + dependencies: + fs-extra "^11.1.1" + tslib "^2.6.0" + +"@docusaurus/types@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.9.2.tgz#e482cf18faea0d1fa5ce0e3f1e28e0f32d2593eb" + integrity sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q== + dependencies: + "@mdx-js/mdx" "^3.0.0" + "@types/history" "^4.7.11" + "@types/mdast" "^4.0.2" + "@types/react" "*" + commander "^5.1.0" + joi "^17.9.2" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" + utility-types "^3.10.0" + webpack "^5.95.0" + webpack-merge "^5.9.0" + +"@docusaurus/utils-common@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.9.2.tgz#e89bfcf43d66359f43df45293fcdf22814847460" + integrity sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw== + dependencies: + "@docusaurus/types" "3.9.2" + tslib "^2.6.0" + +"@docusaurus/utils-validation@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz#04aec285604790806e2fc5aa90aa950dc7ba75ae" + integrity sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A== + dependencies: + "@docusaurus/logger" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + fs-extra "^11.2.0" + joi "^17.9.2" + js-yaml "^4.1.0" + lodash "^4.17.21" + tslib "^2.6.0" + +"@docusaurus/utils@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.9.2.tgz#ffab7922631c7e0febcb54e6d499f648bf8a89eb" + integrity sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ== + dependencies: + "@docusaurus/logger" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + escape-string-regexp "^4.0.0" + execa "5.1.1" + file-loader "^6.2.0" + fs-extra "^11.1.1" + github-slugger "^1.5.0" + globby "^11.1.0" + gray-matter "^4.0.3" + jiti "^1.20.0" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" + p-queue "^6.6.2" + prompts "^2.4.2" + resolve-pathname "^3.0.0" + tslib "^2.6.0" + url-loader "^4.1.1" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@hapi/hoek@^9.0.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" + integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== + +"@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/gen-mapping@^0.3.12": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + +"@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" + integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jsonjoy.com/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" + integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== + +"@jsonjoy.com/buffers@^1.0.0", "@jsonjoy.com/buffers@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz#8d99c7f67eaf724d3428dfd9826c6455266a5c83" + integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA== + +"@jsonjoy.com/codegen@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz#5c23f796c47675f166d23b948cdb889184b93207" + integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g== + +"@jsonjoy.com/json-pack@^1.11.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz#93f8dd57fe3a3a92132b33d1eb182dcd9e7629fa" + integrity sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg== + dependencies: + "@jsonjoy.com/base64" "^1.1.2" + "@jsonjoy.com/buffers" "^1.2.0" + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/json-pointer" "^1.0.2" + "@jsonjoy.com/util" "^1.9.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pointer@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz#049cb530ac24e84cba08590c5e36b431c4843408" + integrity sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg== + dependencies: + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/util" "^1.9.0" + +"@jsonjoy.com/util@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.9.0.tgz#7ee95586aed0a766b746cd8d8363e336c3c47c46" + integrity sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ== + dependencies: + "@jsonjoy.com/buffers" "^1.0.0" + "@jsonjoy.com/codegen" "^1.0.0" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + +"@mdx-js/mdx@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-2.1.1.tgz#6d8b9b75456d7685a52c3812b1c3e4830c7458fb" + integrity sha512-SXC18cChut3F2zkVXwsb2no0fzTQ1z6swjK13XwFbF5QU/SFQM0orAItPypSdL3GvqYyzVJtz8UofzJhPEQtMw== + dependencies: + "@types/estree-jsx" "^0.0.1" + "@types/mdx" "^2.0.0" + astring "^1.6.0" + estree-util-build-jsx "^2.0.0" + estree-util-is-identifier-name "^2.0.0" + estree-walker "^3.0.0" + hast-util-to-estree "^2.0.0" + markdown-extensions "^1.0.0" + periscopic "^3.0.0" + remark-mdx "^2.0.0" + remark-parse "^10.0.0" + remark-rehype "^10.0.0" + unified "^10.0.0" + unist-util-position-from-estree "^1.0.0" + unist-util-stringify-position "^3.0.0" + unist-util-visit "^4.0.0" + vfile "^5.0.0" + +"@mdx-js/mdx@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.1.0.tgz#10235cab8ad7d356c262e8c21c68df5850a97dc3" + integrity sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdx" "^2.0.0" + collapse-white-space "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-util-scope "^1.0.0" + estree-walker "^3.0.0" + hast-util-to-jsx-runtime "^2.0.0" + markdown-extensions "^2.0.0" + recma-build-jsx "^1.0.0" + recma-jsx "^1.0.0" + recma-stringify "^1.0.0" + rehype-recma "^1.0.0" + remark-mdx "^3.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + source-map "^0.7.0" + unified "^11.0.0" + unist-util-position-from-estree "^2.0.0" + unist-util-stringify-position "^4.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +"@mdx-js/react@^1.6.22": + version "1.6.22" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" + integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== + +"@mdx-js/react@^3.0.0", "@mdx-js/react@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.0.tgz#c4522e335b3897b9a845db1dbdd2f966ae8fb0ed" + integrity sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ== + dependencies: + "@types/mdx" "^2.0.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@opentelemetry/api@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + +"@pnpm/config.env-replace@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" + integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== + +"@pnpm/network.ca-file@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983" + integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== + dependencies: + graceful-fs "4.2.10" + +"@pnpm/npm-conf@^2.1.0": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz#bb375a571a0bd63ab0a23bece33033c683e9b6b0" + integrity sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw== + dependencies: + "@pnpm/config.env-replace" "^1.1.0" + "@pnpm/network.ca-file" "^1.0.1" + config-chain "^1.1.11" + +"@polka/url@^1.0.0-next.24": + version "1.0.0-next.28" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" + integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== + +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + +"@sindresorhus/is@^5.2.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668" + integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g== + +"@slorber/remark-comment@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@slorber/remark-comment/-/remark-comment-1.0.0.tgz#2a020b3f4579c89dec0361673206c28d67e08f5a" + integrity sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.1.0" + micromark-util-symbol "^1.0.1" + +"@standard-schema/spec@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" + integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== + +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== + dependencies: + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" + +"@svgr/webpack@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-8.1.0.tgz#16f1b5346f102f89fda6ec7338b96a701d8be0c2" + integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA== + dependencies: + "@babel/core" "^7.21.3" + "@babel/plugin-transform-react-constant-elements" "^7.21.3" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" + +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" + integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + dependencies: + defer-to-connect "^2.0.1" + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/acorn@^4.0.0": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.6.tgz#d61ca5480300ac41a7d973dd5b84d0a591154a22" + integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ== + dependencies: + "@types/estree" "*" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== + dependencies: + "@types/node" "*" + +"@types/buble@^0.20.0": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@types/buble/-/buble-0.20.1.tgz#cba009801fd417b0d2eb8fa6824b537842e05803" + integrity sha512-itmN3lGSTvXg9IImY5j290H+n0B3PpZST6AgEfJJDXfaMx2cdJJZro3/Ay+bZZdIAa25Z5rnoo9rHiPCbANZoQ== + dependencies: + magic-string "^0.25.0" + +"@types/connect-history-api-fallback@^1.5.4": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/d3-scale-chromatic@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39" + integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== + +"@types/d3-scale@^4.0.3": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" + integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== + dependencies: + "@types/d3-time" "*" + +"@types/d3-time@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" + integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== + +"@types/debug@^4.0.0": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" + integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== + dependencies: + "@types/ms" "*" + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.0.tgz#7e41f2481d301c68e14f483fe10b017753ce8d5a" + integrity sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree-jsx@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-0.0.1.tgz#c36d7a1afeb47a95a8ee0b7bc8bc705db38f919d" + integrity sha512-gcLAYiMfQklDCPjQegGn0TBAn9it05ISEsEhlKQUddIk7o2XDokOcTN7HBO8tznM0D9dGezvHEfRZBfZf6me0A== + dependencies: + "@types/estree" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@^0.0.50": + version "0.0.50" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" + integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== + +"@types/estree@^0.0.46": + version "0.0.46" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" + integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== + +"@types/estree@^1.0.0", "@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33": + version "4.19.7" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" + integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/express@^4.17.21": + version "4.17.25" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b" + integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "^1" + +"@types/find-cache-dir@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz#7b959a4b9643a1e6a1a5fe49032693cc36773501" + integrity sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw== + +"@types/fs-extra@^11.0.1": + version "11.0.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.1.tgz#f542ec47810532a8a252127e6e105f487e0a6ea5" + integrity sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA== + dependencies: + "@types/jsonfile" "*" + "@types/node" "*" + +"@types/gtag.js@^0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.12.tgz#095122edca896689bdfcdd73b057e23064d23572" + integrity sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg== + +"@types/hast@^2.0.0": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.2.tgz#236201acca9e2695e42f713d7dd4f151dc2982e4" + integrity sha512-Op5W7jYgZI7AWKY5wQ0/QNMzQM7dGQPyW1rXKNiymVCy5iTfdPuGu4HhYNOM2sIv8gUfIuIdcYlXmAepwaowow== + dependencies: + "@types/unist" "*" + +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + +"@types/html-minifier-terser@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.0.0.tgz#563c1c6c132cd204e71512f9c0b394ff90d3fae7" + integrity sha512-NZwaaynfs1oIoLAV1vg18e7QMVDvw+6SQrdJc8w3BwUaoroVSf6EBj/Sk4PBWGxsq0dzhA2drbsuMC1/6C6KgQ== + +"@types/http-cache-semantics@^4.0.2": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + +"@types/http-proxy@^1.17.8": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" + integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + +"@types/jsonfile@*": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.1.tgz#ac84e9aefa74a2425a0fb3012bdea44f58970f1b" + integrity sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png== + dependencies: + "@types/node" "*" + +"@types/lodash-es@^4.17.6": + version "4.17.7" + resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.7.tgz#22edcae9f44aff08546e71db8925f05b33c7cc40" + integrity sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash.debounce@4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f" + integrity sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA== + dependencies: + "@types/lodash" "*" + +"@types/lodash.escape@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/lodash.escape/-/lodash.escape-4.0.0.tgz#0f60dfe769a6296481e93f27306acea482546efb" + integrity sha512-z4SlV7GOKC7nnPlNoLUVugKwUwa9+XJtLimwq/7OBBzKePMcGsV4QJR5jyoBN4c4Boo6mbmyD6My9IAz3xWy0w== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.184" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" + integrity sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q== + +"@types/mdast@^3.0.0": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.7.tgz#cba63d0cc11eb1605cea5c0ad76e02684394166b" + integrity sha512-YwR7OK8aPmaBvMMUi+pZXBNoW2unbVbfok4YRqGMJBe1dpDlzpRkJrYEYmvjxgs5JhuQmKfDexrN98u941Zasg== + dependencies: + "@types/unist" "*" + +"@types/mdast@^4.0.0", "@types/mdast@^4.0.2": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/mdurl@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + +"@types/mdx@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.1.tgz#e4c05d355d092d7b58db1abfe460e53f41102ac8" + integrity sha512-JPEv4iAl0I+o7g8yVWDwk30es8mfVrjkvh5UeVR2sYPpZCK44vrAPsbJpIS+rJAUxLgaSAMKTEH5Vn5qd9XsrQ== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/ms@*": + version "0.7.31" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "16.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.0.tgz#0d5685f85066f94e97f19e8a67fe003c5fadacc4" + integrity sha512-OyiZPohMMjZEYqcVo/UJ04GyAxXOJEZO/FpzyXxcH4r/ArrVoXHf4MbUrkLp0Tz7/p1mMKpo5zJ6ZHl8XBNthQ== + +"@types/node@^17.0.5": + version "17.0.36" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.36.tgz#c0d5f2fe76b47b63e0e0efc3d2049a9970d68794" + integrity sha512-V3orv+ggDsWVHP99K3JlwtH20R7J4IhI1Kksgc+64q5VxgfRkQG8Ws3MFm/FZOKDYGy9feGFlZ70/HpCNe9QaA== + +"@types/parse5@^5.0.0": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" + integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== + +"@types/prismjs@^1.26.0": + version "1.26.5" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6" + integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ== + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/react-modal@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@types/react-modal/-/react-modal-3.13.1.tgz#5b9845c205fccc85d9a77966b6e16dc70a60825a" + integrity sha512-iY/gPvTDIy6Z+37l+ibmrY+GTV4KQTHcCyR5FIytm182RQS69G5ps4PH2FxtC7bAQ2QRHXMevsBgck7IQruHNg== + dependencies: + "@types/react" "*" + +"@types/react-router-config@*": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" + integrity sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router-config@^5.0.7": + version "5.0.11" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.11.tgz#2761a23acc7905a66a94419ee40294a65aaa483a" + integrity sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "^5.1.0" + +"@types/react-router-dom@*": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.18" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" + integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react-router@^5.1.0": + version "5.1.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react@*": + version "18.0.9" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878" + integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/retry@0.12.2": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" + integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== + +"@types/sax@^1.2.1": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.3.tgz#b630ac1403ebd7812e0bf9a10de9bf5077afb348" + integrity sha512-+QSw6Tqvs/KQpZX8DvIl3hZSjNFLW/OqE5nlyHXtTwODaJvioN2rOWpBNEWZp2HZUFhOh+VohmJku/WxEXU2XA== + dependencies: + "@types/node" "*" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/semver@^7.3.13": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + +"@types/send@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/send@<1": + version "0.17.6" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.6.tgz#aeb5385be62ff58a52cd5459daa509ae91651d25" + integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== + dependencies: + "@types/express" "*" + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@^1", "@types/serve-static@^1.15.5": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" + integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "<1" + +"@types/sockjs@^0.3.36": + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== + dependencies: + "@types/node" "*" + +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + +"@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/which@3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/which/-/which-3.0.4.tgz#2c3a89be70c56a84a6957a7264639f39ae4340a1" + integrity sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w== + +"@types/ws@^8.5.10": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" + integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@types/yarnpkg__lockfile@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.5.tgz#9639020e1fb65120a2f4387db8f1e8b63efdf229" + integrity sha512-8NYnGOctzsI4W0ApsP/BIHD/LnxpJ6XaGf2AZmz4EyDYJMxtprN4279dLNI1CPZcwC9H18qYcaFv4bXi0wmokg== + +"@ungap/structured-clone@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@vercel/oidc@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vercel/oidc/-/oidc-3.0.3.tgz#82c2b6dd4d5c3b37dcb1189718cdeb9db402d052" + integrity sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg== + +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== + +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" + +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.4, accepts@~1.3.5: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.1.1.tgz#3ddab7f84e4a7e2313f6c414c5b7dac85f4e3ebc" + integrity sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w== + +acorn@^8.0.0, acorn@^8.0.4: + version "8.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" + integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== + +acorn@^8.14.0, acorn@^8.8.2: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +address@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ai@5.0.86, ai@^5.0.30: + version "5.0.86" + resolved "https://registry.yarnpkg.com/ai/-/ai-5.0.86.tgz#0edb696bc872c39047f76c9ccaf4be21979653e9" + integrity sha512-ooHwNTkLdedFf98iQhtSc5btc/P4UuXuOpYneoifq0190vqosLunNdW8Hs6CiE0Am7YOGNplDK56JIPlHZIL4w== + dependencies: + "@ai-sdk/gateway" "2.0.5" + "@ai-sdk/provider" "2.0.0" + "@ai-sdk/provider-utils" "3.0.15" + "@opentelemetry/api" "1.9.0" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.0.0, ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.8.0: + version "8.8.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" + integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +algoliasearch-helper@^3.26.0: + version "3.26.0" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.26.0.tgz#d6e283396a9fc5bf944f365dc3b712570314363f" + integrity sha512-Rv2x3GXleQ3ygwhkhJubhhYGsICmShLAiqtUuJTUkr9uOCOXyF2E71LVT4XDnVffbknv8XgScP4U0Oxtgm+hIw== + dependencies: + "@algolia/events" "^4.0.1" + +algoliasearch@^5.28.0, algoliasearch@^5.37.0: + version "5.42.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.42.0.tgz#c30247e6480030471dfa2899f591878ff2fc5e15" + integrity sha512-X5+PtWc9EJIPafT/cj8ZG+6IU3cjRRnlHGtqMHK/9gsiupQbAyYlH5y7qt/FtsAhfX5AICHffZy69ZAsVrxWkQ== + dependencies: + "@algolia/abtesting" "1.8.0" + "@algolia/client-abtesting" "5.42.0" + "@algolia/client-analytics" "5.42.0" + "@algolia/client-common" "5.42.0" + "@algolia/client-insights" "5.42.0" + "@algolia/client-personalization" "5.42.0" + "@algolia/client-query-suggestions" "5.42.0" + "@algolia/client-search" "5.42.0" + "@algolia/ingestion" "1.42.0" + "@algolia/monitoring" "1.42.0" + "@algolia/recommend" "5.42.0" + "@algolia/requester-browser-xhr" "5.42.0" + "@algolia/requester-fetch" "5.42.0" + "@algolia/requester-node-http" "5.42.0" + +ansi-align@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + +ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.0, ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +arg@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.0.tgz#a20e2bb5710e82950a516b3f933fee5ed478be90" + integrity sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +assert@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" + integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A== + dependencies: + es6-object-assign "^1.1.0" + is-nan "^1.2.1" + object-is "^1.0.1" + util "^0.12.0" + +astring@^1.6.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.8.1.tgz#a91c4afd4af3523e11f31242a3d5d9af62bb6cc6" + integrity sha512-Aj3mbwVzj7Vve4I/v2JYOPFkCGM2YS7OqQTNSxmUR+LECRpokuPgAYghePgr6SALDo5bD5DlfbSaYjOzGJZOLQ== + +astring@^1.8.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef" + integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== + +autocomplete.js@^0.37.0: + version "0.37.1" + resolved "https://registry.yarnpkg.com/autocomplete.js/-/autocomplete.js-0.37.1.tgz#a29a048d827e7d2bf8f7df8b831766e5cc97df01" + integrity sha512-PgSe9fHYhZEsm/9jggbjtVsGXJkPLvd+9mC7gZJ662vVL5CRWEtm/mIrrzCx0MrNxHVwxD5d00UOn6NsmL2LUQ== + dependencies: + immediate "^3.2.3" + +autoprefixer@^10.4.19, autoprefixer@^10.4.21: + version "10.4.21" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.21.tgz#77189468e7a8ad1d9a37fbc08efc9f480cf0a95d" + integrity sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ== + dependencies: + browserslist "^4.24.4" + caniuse-lite "^1.0.30001702" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.1.1" + postcss-value-parser "^4.2.0" + +available-typed-arrays@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9" + integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA== + +babel-loader@^9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.2.1.tgz#04c7835db16c246dd19ba0914418f3937797587b" + integrity sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA== + dependencies: + find-cache-dir "^4.0.0" + schema-utils "^4.0.0" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.12" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz#ca55bbec8ab0edeeef3d7b8ffd75322e210879a9" + integrity sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.3" + semver "^6.3.1" + +babel-plugin-polyfill-corejs2@^0.4.14: + version "0.4.14" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz#8101b82b769c568835611542488d463395c2ef8f" + integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== + dependencies: + "@babel/compat-data" "^7.27.7" + "@babel/helper-define-polyfill-provider" "^0.6.5" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz#4e4e182f1bb37c7ba62e2af81d8dd09df31344f6" + integrity sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.3" + core-js-compat "^3.40.0" + +babel-plugin-polyfill-corejs3@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164" + integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.5" + core-js-compat "^3.43.0" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz#abeb1f3f1c762eace37587f42548b08b57789bc8" + integrity sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.3" + +babel-plugin-polyfill-regenerator@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" + integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.5" + +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +baseline-browser-mapping@^2.8.19: + version "2.8.23" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz#cd43e17eff5cbfb67c92153e7fe856cf6d426421" + integrity sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ== + +baseline-browser-mapping@^2.8.25: + version "2.8.27" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.27.tgz#d15ab8face053137f8eb4c028455024787515d5d" + integrity sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA== + +bash-glob@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bash-glob/-/bash-glob-2.0.0.tgz#a8ef19450783403ed93fccca2dbe09f2cf6320dc" + integrity sha512-53/NJ+t2UAkEYgQPO6aFjbx1Ue8vNNXCYaA4EljNKP1SR8A9dSQQoBmYWR8BLXO0/NDRJEMSJ4BxWihi//m3Kw== + dependencies: + bash-path "^1.0.1" + component-emitter "^1.2.1" + cross-spawn "^5.1.0" + each-parallel-async "^1.0.0" + extend-shallow "^2.0.1" + is-extglob "^2.1.1" + is-glob "^4.0.0" + +bash-path@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bash-path/-/bash-path-1.0.3.tgz#dbc9efbdf18b1c11413dcb59b960e6aa56c84258" + integrity sha512-mGrYvOa6yTY/qNCiZkPFJqWmODK68y6kmVRAJ1NNbWlNoJrUrsFxu7FU2EKg7gbrer6ttrKkF2s/E/lhRy7/OA== + dependencies: + arr-union "^3.1.0" + is-windows "^1.0.1" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + +bcp-47-match@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bcp-47-match/-/bcp-47-match-1.0.3.tgz#cb8d03071389a10aff2062b862d6575ffd7cd7ef" + integrity sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722" + integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA== + dependencies: + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + +boxen@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.1.1.tgz#f9ba525413c2fec9cdb88987d835c4f7cad9c8f4" + integrity sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog== + dependencies: + ansi-align "^3.0.1" + camelcase "^7.0.1" + chalk "^5.2.0" + cli-boxes "^3.0.0" + string-width "^5.1.2" + type-fest "^2.13.0" + widest-line "^4.0.1" + wrap-ansi "^8.1.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.0.0: + version "4.16.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.8.tgz#cb868b0b554f137ba6e33de0ecff2eda403c4fb0" + integrity sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ== + dependencies: + caniuse-lite "^1.0.30001251" + colorette "^1.3.0" + electron-to-chromium "^1.3.811" + escalade "^3.1.1" + node-releases "^1.1.75" + +browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + +browserslist@^4.26.0: + version "4.27.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.27.0.tgz#755654744feae978fbb123718b2f139bc0fa6697" + integrity sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw== + dependencies: + baseline-browser-mapping "^2.8.19" + caniuse-lite "^1.0.30001751" + electron-to-chromium "^1.5.238" + node-releases "^2.0.26" + update-browserslist-db "^1.1.4" + +browserslist@^4.26.3: + version "4.28.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929" + integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== + dependencies: + baseline-browser-mapping "^2.8.25" + caniuse-lite "^1.0.30001754" + electron-to-chromium "^1.5.249" + node-releases "^2.0.27" + update-browserslist-db "^1.1.4" + +buble@0.19.6: + version "0.19.6" + resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.6.tgz#915909b6bd5b11ee03b1c885ec914a8b974d34d3" + integrity sha512-9kViM6nJA1Q548Jrd06x0geh+BG2ru2+RMDkIHHgJY/8AcyCs34lTHwra9BX7YdPrZXd5aarkpr/SY8bmPgPdg== + dependencies: + chalk "^2.4.1" + magic-string "^0.25.1" + minimist "^1.2.0" + os-homedir "^1.0.1" + regexpu-core "^4.2.0" + vlq "^1.0.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bundle-name@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" + integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== + dependencies: + run-applescript "^7.0.0" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacheable-lookup@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" + integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w== + +cacheable-request@^10.2.8: + version "10.2.14" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-10.2.14.tgz#eb915b665fda41b79652782df3f553449c406b9d" + integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ== + dependencies: + "@types/http-cache-semantics" "^4.0.2" + get-stream "^6.0.1" + http-cache-semantics "^4.1.1" + keyv "^4.5.3" + mimic-response "^4.0.0" + normalize-url "^8.0.0" + responselike "^3.0.0" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + +camelcase@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" + integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001688, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001751: + version "1.0.30001753" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz" + integrity sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw== + +caniuse-lite@^1.0.30001754: + version "1.0.30001754" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz#7758299d9a72cce4e6b038788a15b12b44002759" + integrity sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +chalk@^2.0.0, chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^5.0.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + +chalk@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" + integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.1.tgz#98724833e1e27990dee0bd0f2b8a859c3476aac7" + integrity sha512-OzmutCf2Kmc+6DrFrrPS8/tDh2+DpnrfzdICHWhcVC9eOd0N1PXmQEE1a8iM4IziIAG+8tmTq3K+oo0ubH6RRQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@1.0.0-rc.12: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +classnames@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + +clean-css@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.1.5.tgz#3b0af240dcfc9a3779a08c2332df3ebd4474f232" + integrity sha512-9dr/cU/LjMpU57PXlSvDkVRh0rPxJBXiBtD0+SgYt8ahTCsXtfKjCkNYgIoTC6mBg8CFr5EKhW3DKCaGMUbUfQ== + dependencies: + source-map "~0.6.0" + +clean-css@^5.3.3, clean-css@~5.3.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" + integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-table3@^0.6.3: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clsx@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + +clsx@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +collapse-white-space@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca" + integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colord@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +colorette@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" + integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== + +colorette@^2.0.10: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + +colors@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combine-promises@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" + integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg== + +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + +comma-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98" + integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg== + +commander@7, commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1" + integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA== + +commander@^2.11.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +commander@^8.1.0, commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +component-props@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/component-props/-/component-props-1.1.1.tgz#f9b7df9b9927b6e6d97c9bd272aa867670f34944" + integrity sha1-+bffm5kntubZfJvScqqGdnDzSUQ= + +component-xor@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/component-xor/-/component-xor-0.0.4.tgz#c55d83ccc1b94cd5089a4e93fa7891c7263e59aa" + integrity sha1-xV2DzMG5TNUImk6T+niRxyY+Wao= + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +config-chain@^1.1.11: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +configstore@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-6.0.0.tgz#49eca2ebc80983f77e09394a1a56e0aca8235566" + integrity sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA== + dependencies: + dot-prop "^6.0.1" + graceful-fs "^4.2.6" + unique-string "^3.0.0" + write-file-atomic "^3.0.3" + xdg-basedir "^5.0.1" + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +consola@^3.2.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.0.tgz#4cfc9348fd85ed16a17940b3032765e31061ab88" + integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA== + +console-control-strings@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + +core-js-compat@^3.40.0: + version "3.41.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.41.0.tgz#4cdfce95f39a8f27759b667cf693d96e5dda3d17" + integrity sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A== + dependencies: + browserslist "^4.24.4" + +core-js-compat@^3.43.0: + version "3.46.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.46.0.tgz#0c87126a19a1af00371e12b02a2b088a40f3c6f7" + integrity sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law== + dependencies: + browserslist "^4.26.3" + +core-js-pure@^3.30.2: + version "3.41.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.41.0.tgz#349fecad168d60807a31e83c99d73d786fe80811" + integrity sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q== + +core-js@^3.14.0: + version "3.16.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.16.1.tgz#f4485ce5c9f3c6a7cb18fa80488e08d362097249" + integrity sha512-AAkP8i35EbefU+JddyWi12AWE9f2N/qr/pwnDtWz4nyUIBGMJPX99ANFFRSw6FefM374lDujdtLDyhN2A/btHw== + +core-js@^3.31.1: + version "3.41.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.41.0.tgz#57714dafb8c751a6095d028a7428f1fb5834a776" + integrity sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cose-base@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a" + integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg== + dependencies: + layout-base "^1.0.0" + +cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-4.0.0.tgz#5a3cc53d7dd86183df5da0312816ceeeb5bb1fc2" + integrity sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA== + dependencies: + type-fest "^1.0.1" + +css-blank-pseudo@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz#32020bff20a209a53ad71b8675852b49e8d57e46" + integrity sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag== + dependencies: + postcss-selector-parser "^7.0.0" + +css-declaration-sorter@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz#6dec1c9523bc4a643e088aab8f09e67a54961024" + integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow== + +css-has-pseudo@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz#a5ee2daf5f70a2032f3cefdf1e36e7f52a243873" + integrity sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.2.0" + +css-loader@^6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" + integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.33" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.5.4" + +css-minimizer-webpack-plugin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz#33effe662edb1a0bf08ad633c32fa75d0f7ec565" + integrity sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + cssnano "^6.0.1" + jest-worker "^29.4.3" + postcss "^8.4.24" + schema-utils "^4.0.1" + serialize-javascript "^6.0.1" + +css-prefers-color-scheme@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz#ba001b99b8105b8896ca26fc38309ddb2278bd3c" + integrity sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ== + +css-select@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" + integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== + dependencies: + boolbase "^1.0.0" + css-what "^5.0.0" + domhandler "^4.2.0" + domutils "^2.6.0" + nth-check "^2.0.0" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-selector-parser@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.4.1.tgz#03f9cb8a81c3e5ab2c51684557d5aaf6d2569759" + integrity sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g== + +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" + integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssdb@^8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.4.2.tgz#1a367ab1904c97af0bb2c7ae179764deae7b078b" + integrity sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-advanced@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz#82b090872b8f98c471f681d541c735acf8b94d3f" + integrity sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ== + dependencies: + autoprefixer "^10.4.19" + browserslist "^4.23.0" + cssnano-preset-default "^6.1.2" + postcss-discard-unused "^6.0.5" + postcss-merge-idents "^6.0.3" + postcss-reduce-idents "^6.0.3" + postcss-zindex "^6.0.2" + +cssnano-preset-default@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz#adf4b89b975aa775f2750c89dbaf199bbd9da35e" + integrity sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg== + dependencies: + browserslist "^4.23.0" + css-declaration-sorter "^7.2.0" + cssnano-utils "^4.0.2" + postcss-calc "^9.0.1" + postcss-colormin "^6.1.0" + postcss-convert-values "^6.1.0" + postcss-discard-comments "^6.0.2" + postcss-discard-duplicates "^6.0.3" + postcss-discard-empty "^6.0.3" + postcss-discard-overridden "^6.0.2" + postcss-merge-longhand "^6.0.5" + postcss-merge-rules "^6.1.1" + postcss-minify-font-values "^6.1.0" + postcss-minify-gradients "^6.0.3" + postcss-minify-params "^6.1.0" + postcss-minify-selectors "^6.0.4" + postcss-normalize-charset "^6.0.2" + postcss-normalize-display-values "^6.0.2" + postcss-normalize-positions "^6.0.2" + postcss-normalize-repeat-style "^6.0.2" + postcss-normalize-string "^6.0.2" + postcss-normalize-timing-functions "^6.0.2" + postcss-normalize-unicode "^6.1.0" + postcss-normalize-url "^6.0.2" + postcss-normalize-whitespace "^6.0.2" + postcss-ordered-values "^6.0.2" + postcss-reduce-initial "^6.1.0" + postcss-reduce-transforms "^6.0.2" + postcss-svgo "^6.0.3" + postcss-unique-selectors "^6.0.4" + +cssnano-utils@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz#56f61c126cd0f11f2eef1596239d730d9fceff3c" + integrity sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ== + +cssnano@^6.0.1, cssnano@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.1.2.tgz#4bd19e505bd37ee7cf0dc902d3d869f6d79c66b8" + integrity sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA== + dependencies: + cssnano-preset-default "^6.1.2" + lilconfig "^3.1.1" + +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + +csstype@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" + integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + +cytoscape-cose-bilkent@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz#762fa121df9930ffeb51a495d87917c570ac209b" + integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ== + dependencies: + cose-base "^1.0.0" + +cytoscape@^3.28.1: + version "3.31.1" + resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.31.1.tgz#29b12cac715fbb2aacc50cdf5cf1467aadde9c00" + integrity sha512-Hx5Mtb1+hnmAKaZZ/7zL1Y5HTFYOjdDswZy/jD+1WINRU8KVi1B7+vlHdsTwY+VCFucTreoyu1RDzQJ9u0d2Hw== + +"d3-array@1 - 2": + version "2.12.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" + integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== + dependencies: + internmap "^1.0.0" + +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.1.6.tgz#0342c835925826f49b4d16eb7027aec334ffc97d" + integrity sha512-DCbBBNuKOeiR9h04ySRBMW52TFVc91O9wJziuyXw6Ztmy8D3oZbmCkOO3UHKC7ceNJsN2Mavo9+vwV8EAEUXzA== + dependencies: + internmap "1 - 2" + +d3-array@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" + integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-geo@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.0.1.tgz#4f92362fd8685d93e3b1fae0fd97dc8980b1ed7e" + integrity sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@1: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +"d3-path@1 - 3", d3-path@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" + integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-sankey@^0.12.3: + version "0.12.3" + resolved "https://registry.yarnpkg.com/d3-sankey/-/d3-sankey-0.12.3.tgz#b3c268627bd72e5d80336e8de6acbfec9d15d01d" + integrity sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ== + dependencies: + d3-array "1 - 2" + d3-shape "^1.2.0" + +d3-scale-chromatic@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" + integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556" + integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ== + dependencies: + d3-path "1 - 3" + +d3-shape@^1.2.0: + version "1.3.7" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" + integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.4.0, d3@^7.8.2: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + +dagre-d3-es@7.0.10: + version "7.0.10" + resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz#19800d4be674379a3cd8c86a8216a2ac6827cadc" + integrity sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A== + dependencies: + d3 "^7.8.2" + lodash-es "^4.17.21" + +dayjs@^1.11.7: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.3.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^4.0.0, debug@^4.1.0, debug@^4.1.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +debug@^4.4.1: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +decode-named-character-reference@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.1.tgz#57b2bd9112659cacbc449d3577d7dadb8e1f3d1b" + integrity sha512-YV/0HQHreRwKb7uBopyIkLG17jG6Sv2qUchk9qSoVJ2f+flwRsPNBO0hAnjt6mTNYUT+vw9Gy2ihXg4sUWPi2w== + dependencies: + character-entities "^2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +default-browser-id@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" + integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== + +default-browser@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" + integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== + dependencies: + bundle-name "^4.1.0" + default-browser-id "^5.0.0" + +defer-to-connect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +delaunator@5: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" + integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== + dependencies: + robust-predicates "^3.0.0" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +dequal@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d" + integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug== + +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detect-port@^1.5.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.6.1.tgz#45e4073997c5f292b957cb678fb0bb8ed4250a67" + integrity sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q== + dependencies: + address "^1.0.1" + debug "4" + +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +diff@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +direction@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/direction/-/direction-1.0.4.tgz#2b86fb686967e987088caf8b89059370d4837442" + integrity sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ== + +dns-packet@^5.2.2: + version "5.5.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.5.0.tgz#f59cbf3396c130957c56a6ad5fd3959ccdc30065" + integrity sha512-USawdAUzRkV6xrqTjiAEp6M9YagZEzWcSUaZTcIFAiyQWW1SoI6KyId8y2+/71wbgHKQAKd+iupLv4YvEwYWvA== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +docusaurus-lunr-search@^2.3.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/docusaurus-lunr-search/-/docusaurus-lunr-search-2.4.2.tgz#6a9ccaa50eb7140ce6f71ff5cb395fd53c33a5e6" + integrity sha512-t6Uk45ED5gZ4ma5s5fEzHrf52QmoTpKSC7LnskaSBqyFL3uj5ciW14WOm3nE/dlhkzx+ZphLjOEoRXgkwaSy7Q== + dependencies: + autocomplete.js "^0.37.0" + clsx "^1.2.1" + gauge "^3.0.0" + hast-util-select "^4.0.0" + hast-util-to-text "^2.0.0" + hogan.js "^3.0.2" + lunr "^2.3.8" + lunr-languages "^1.4.0" + minimatch "^3.0.4" + object-assign "^4.1.1" + rehype-parse "^7.0.1" + to-vfile "^6.1.0" + unified "^9.0.0" + unist-util-is "^4.0.2" + +docusaurus-plugin-internaldocs-fb@1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/docusaurus-plugin-internaldocs-fb/-/docusaurus-plugin-internaldocs-fb-1.19.1.tgz#6a652d8ac918a971beaa24753df323c72049dbcf" + integrity sha512-hVLP1hpS1MkOujONQLNLRf2jbRymhdhcI69UpK1abT7F4/5i26Bl1EpxvhwPE0JSSCNjpADs9x+aWiDcPe8IyQ== + dependencies: + "@mdx-js/mdx" "^2.1.1" + "@mdx-js/react" "^1.6.22" + "@types/lodash.debounce" "4.0.7" + "@types/lodash.escape" "4.0.0" + "@types/react-modal" "3.13.1" + "@types/which" "3.0.4" + assert "^2.0.0" + buffer "^6.0.3" + clsx "^1.2.1" + docusaurus-lunr-search "^2.3.2" + fs-extra "^10.1.0" + lodash.debounce "^4.0.8" + lodash.escape "^4.0.0" + mermaid "^10.9.0" + node-fetch "2.6.7" + path-browserify "^1.0.1" + react-live "^2.2.3" + react-modal "3.15.1" + remark-gfm "^3.0.1" + remark-mdx-filter-imports "^0.1.3" + unified "^9.2.1" + unist-util-remove "^3.1.0" + unist-util-visit "^2.0.1" + validate-peer-dependencies "^2.1.0" + which "4.0.0" + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dom-iterator/-/dom-iterator-1.0.0.tgz#9c09899846ec41c2d257adc4d6015e4759ef05ad" + integrity sha512-7dsMOQI07EMU98gQM8NSB3GsAiIeBYIPKpnxR3c9xOvdvBjChAcOM0iJ222I3p5xyiZO9e5oggkNaCusuTdYig== + dependencies: + component-props "1.1.1" + component-xor "0.0.4" + +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" + integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA== + dependencies: + domelementtype "^2.2.0" + +domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +"dompurify@^3.0.5 <3.1.7": + version "3.1.6" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2" + integrity sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ== + +domutils@^2.5.2, domutils@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.7.0.tgz#8ebaf0c41ebafcf55b0b72ec31c56323712c5442" + integrity sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +domutils@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" + integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.1" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +each-parallel-async@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/each-parallel-async/-/each-parallel-async-1.0.0.tgz#91783e190000c7dd588336b2d468ebaf71980f7b" + integrity sha512-P/9kLQiQj0vZNzphvKKTgRgMnlqs5cJsxeAiuog1jrUnwv0Z3hVUwJDQiP7MnLb2I9S15nR9SRUceFT9IxtqRg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.811: + version "1.3.813" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.813.tgz#751a007d71c00faed8b5e9edaf3634c14b9c5a1f" + integrity sha512-YcSRImHt6JZZ2sSuQ4Bzajtk98igQ0iKkksqlzZLzbh4p0OIyJRSvUbsgqfcR8txdfsoYCc4ym306t4p2kP/aw== + +electron-to-chromium@^1.5.238: + version "1.5.244" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz#b9b61e3d24ef4203489951468614f2a360763820" + integrity sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw== + +electron-to-chromium@^1.5.249: + version "1.5.250" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz#0b40436fa41ae7cbac3d2f60ef0411a698eb72a7" + integrity sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw== + +electron-to-chromium@^1.5.73: + version "1.5.119" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.119.tgz#4e105e419209b33e1c44b4d1b5fc6fb27fac0209" + integrity sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ== + +elkjs@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.9.3.tgz#16711f8ceb09f1b12b99e971b138a8384a529161" + integrity sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +emoticon@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-4.1.0.tgz#d5a156868ee173095627a33de3f1e914c3dde79e" + integrity sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +enhanced-resolve@^5.17.1: + version "5.18.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf" + integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^4.2.0, entities@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.0.tgz#62915f08d67353bb4eb67e3d62641a4059aec656" + integrity sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg== + +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +eol@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" + integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.18.5: + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-object-assign@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" + integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= + +esast-util-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz#8d1cfb51ad534d2f159dc250e604f3478a79f1ad" + integrity sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + unist-util-position-from-estree "^2.0.0" + +esast-util-from-js@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz#5147bec34cc9da44accf52f87f239a40ac3e8225" + integrity sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw== + dependencies: + "@types/estree-jsx" "^1.0.0" + acorn "^8.0.0" + esast-util-from-estree "^2.0.0" + vfile-message "^4.0.0" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-goat@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-4.0.0.tgz#9424820331b510b0666b98f7873fe11ac4aa8081" + integrity sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +estree-util-attach-comments@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-2.0.0.tgz#2c06d484dfcf841b5946bcb84d5412cbcd544e22" + integrity sha512-kT9YVRvlt2ewPp9BazfIIgXMGsXOEpOm57bK8aa4F3eOEndMml2JAETjWaG3SZYHmC6axSNIzHGY718dYwIuVg== + dependencies: + "@types/estree" "^0.0.46" + +estree-util-attach-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz#344bde6a64c8a31d15231e5ee9e297566a691c2d" + integrity sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw== + dependencies: + "@types/estree" "^1.0.0" + +estree-util-build-jsx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-2.0.0.tgz#4903e2a923ebc791f86e78ec3687d01715dec902" + integrity sha512-d49hPGqBCJF/bF06g1Ywg7zjH1mrrUdPPrixBlKBxcX4WvMYlUUJ8BkrwlzWc8/fm6XqGgk5jilhgeZBDEGwOQ== + dependencies: + "@types/estree-jsx" "^0.0.1" + estree-util-is-identifier-name "^2.0.0" + estree-walker "^3.0.0" + +estree-util-build-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz#b6d0bced1dcc4f06f25cf0ceda2b2dcaf98168f1" + integrity sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-walker "^3.0.0" + +estree-util-is-identifier-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.0.0.tgz#e2d3d2ae3032c017b2112832bfc5d8ba938c8010" + integrity sha512-aXXZFVMnBBDRP81vS4YtAYJ0hUkgEsXea7lNKWCOeaAquGb1Jm2rcONPB5fpzwgbNxulTvrWuKnp9UElUGAKeQ== + +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + +estree-util-scope@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/estree-util-scope/-/estree-util-scope-1.0.0.tgz#9cbdfc77f5cb51e3d9ed4ad9c4adbff22d43e585" + integrity sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + +estree-util-to-js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz#10a6fb924814e6abb62becf0d2bc4dea51d04f17" + integrity sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg== + dependencies: + "@types/estree-jsx" "^1.0.0" + astring "^1.8.0" + source-map "^0.7.0" + +estree-util-value-to-estree@^3.0.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/estree-util-value-to-estree/-/estree-util-value-to-estree-3.3.2.tgz#75bb2263850b6f5ac35edd343929c36b51a69806" + integrity sha512-hYH1aSvQI63Cvq3T3loaem6LW4u72F187zW4FHpTrReJSm6W66vYTFNO1vH/chmcOulp1HlAj1pxn8Ag0oXI5Q== + dependencies: + "@types/estree" "^1.0.0" + +estree-util-visit@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-1.1.0.tgz#c0ea7942c40ac7889a77b57a11e92f987744bc6f" + integrity sha512-3lXJ4Us9j8TUif9cWcQy81t9p5OLasnDuuhrFiqb+XstmKC1d1LmrQWYsY49/9URcfHE64mPypDBaNK9NwWDPQ== + dependencies: + "@types/estree-jsx" "^0.0.1" + "@types/unist" "^2.0.0" + +estree-util-visit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz#13a9a9f40ff50ed0c022f831ddf4b58d05446feb" + integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/unist" "^3.0.0" + +estree-walker@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.1.tgz#c2a9fb4a30232f5039b7c030b37ead691932debd" + integrity sha512-woY0RUD87WzMBUiZLx8NsYr23N5BKsOMZHhu2hoNRVh6NXGfoiT1KOL8G3UHlJAnEDGmfa5ubNA/AacfG+Kb0g== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eta@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eta/-/eta-2.2.0.tgz#eb8b5f8c4e8b6306561a455e62cd7492fe3a9b8a" + integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +eval@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" + integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== + dependencies: + "@types/node" "*" + require-like ">= 0.1.1" + +eventemitter3@^4.0.0, eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +eventsource-parser@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + +execa@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exenv@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== + +express@^4.21.2: + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.7.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.12" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.11, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + +fastq@^1.6.0: + version "1.11.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.1.tgz#5d8175aae17db61947f8b162cfc7f63264d22807" + integrity sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw== + dependencies: + reusify "^1.0.4" + +fault@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c" + integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ== + dependencies: + format "^0.2.0" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +feed@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" + integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== + dependencies: + xml-js "^1.6.11" + +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz#a30ee0448f81a3990708f6453633c733e2f6eec2" + integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg== + dependencies: + common-path-prefix "^3.0.0" + pkg-dir "^7.0.0" + +find-up@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790" + integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw== + dependencies: + locate-path "^7.1.0" + path-exists "^5.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +follow-redirects@^1.0.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + +form-data-encoder@^2.1.2: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" + integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== + +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^11.1.1, fs-extra@^11.2.0: + version "11.3.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" + integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +github-slugger@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" + integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regex.js@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz#2b323728271d133830850e32311f40766c5f6413" + integrity sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ== + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globby@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.1.tgz#7c44a93869b0b7612e38f22ed532bfe37b25ea6f" + integrity sha512-XMzoDZbGZ37tufiv7g0N4F/zp3zkwdFtVbV3EHsVl1KQr4RPLfNoT068/97RPshz2J5xYNEjLKKBKaGHifBd3Q== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + +globby@^13.1.3: + version "13.1.3" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff" + integrity sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +got@^12.1.0: + version "12.6.1" + resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549" + integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ== + dependencies: + "@sindresorhus/is" "^5.2.0" + "@szmarczak/http-timer" "^5.0.1" + cacheable-lookup "^7.0.0" + cacheable-request "^10.2.8" + decompress-response "^6.0.0" + form-data-encoder "^2.1.2" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" + responselike "^3.0.0" + +graceful-fs@4.2.10, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + +graceful-fs@^4.2.11: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +gray-matter@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +has-yarn@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-3.0.0.tgz#c3c21e559730d1d3b57e28af1f30d06fac38147d" + integrity sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hast-util-from-parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" + integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== + dependencies: + "@types/parse5" "^5.0.0" + hastscript "^6.0.0" + property-information "^5.0.0" + vfile "^4.0.0" + vfile-location "^3.2.0" + web-namespaces "^1.0.0" + +hast-util-from-parse5@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^9.0.0" + property-information "^7.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + +hast-util-has-property@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-1.0.4.tgz#9f137565fad6082524b382c1e7d7d33ca5059f36" + integrity sha512-ghHup2voGfgFoHMGnaLHOjbYFACKrRh9KFttdCzMCbFoBMJXiNi2+XTrPP8+q6cDJM/RSqlCfVWrjp1H201rZg== + +hast-util-is-element@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425" + integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ== + +hast-util-parse-selector@^2.0.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" + integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== + +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-raw@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e" + integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-from-parse5 "^8.0.0" + hast-util-to-parse5 "^8.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + parse5 "^7.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-select@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/hast-util-select/-/hast-util-select-4.0.2.tgz#ae3ef2860e02cda2ad3a2e72b47c1f5e8f44e9e7" + integrity sha512-8EEG2//bN5rrzboPWD2HdS3ugLijNioS1pqOTIolXNf67xxShYw4SQEmVXd3imiBG+U2bC2nVTySr/iRAA7Cjg== + dependencies: + bcp-47-match "^1.0.0" + comma-separated-tokens "^1.0.0" + css-selector-parser "^1.0.0" + direction "^1.0.0" + hast-util-has-property "^1.0.0" + hast-util-is-element "^1.0.0" + hast-util-to-string "^1.0.0" + hast-util-whitespace "^1.0.0" + not "^0.1.0" + nth-check "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + unist-util-visit "^2.0.0" + zwitch "^1.0.0" + +hast-util-to-estree@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-2.0.2.tgz#79c5bf588915610b3f0d47ca83a74dc0269c7dc2" + integrity sha512-UQrZVeBj6A9od0lpFvqHKNSH9zvDrNoyWKbveu1a2oSCXEDUI+3bnd6BoiQLPnLrcXXn/jzJ6y9hmJTTlvf8lQ== + dependencies: + "@types/estree-jsx" "^0.0.1" + "@types/hast" "^2.0.0" + "@types/unist" "^2.0.0" + comma-separated-tokens "^2.0.0" + estree-util-attach-comments "^2.0.0" + estree-util-is-identifier-name "^2.0.0" + hast-util-whitespace "^2.0.0" + mdast-util-mdx-expression "^1.0.0" + mdast-util-mdxjs-esm "^1.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^0.3.0" + unist-util-position "^4.0.0" + zwitch "^2.0.0" + +hast-util-to-estree@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz#e654c1c9374645135695cc0ab9f70b8fcaf733d7" + integrity sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-attach-comments "^3.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + zwitch "^2.0.0" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-to-parse5@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed" + integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-to-string@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-1.0.4.tgz#9b24c114866bdb9478927d7e9c36a485ac728378" + integrity sha512-eK0MxRX47AV2eZ+Lyr18DCpQgodvaS3fAQO2+b9Two9F5HEoRPhiUMNzoXArMJfZi2yieFzUBMRl3HNJ3Jus3w== + +hast-util-to-text@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-2.0.1.tgz#04f2e065642a0edb08341976084aa217624a0f8b" + integrity sha512-8nsgCARfs6VkwH2jJU9b8LNTuR4700na+0h3PqCaEk4MAnMDeu5P0tP8mjk9LLNGxIeQRLbiDbZVw6rku+pYsQ== + dependencies: + hast-util-is-element "^1.0.0" + repeat-string "^1.0.0" + unist-util-find-after "^3.0.0" + +hast-util-whitespace@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" + integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== + +hast-util-whitespace@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c" + integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg== + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hogan.js@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd" + integrity sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg== + dependencies: + mkdirp "0.3.0" + nopt "1.0.10" + +hoist-non-react-statics@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-escaper@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.0.2.tgz#14059ad64b69bf9f8b8a33f25b53411d8321e75d" + integrity sha512-AgYO3UGhMYQx2S/FBJT3EM0ZYcKmH6m9XL9c1v77BeK/tYJxGPxT1/AtsdUi4FcP8kZGmqqnItCcjFPcX9hk6A== + dependencies: + camel-case "^4.1.2" + clean-css "^5.1.5" + commander "^8.1.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.7.2" + +html-minifier-terser@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942" + integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA== + dependencies: + camel-case "^4.1.2" + clean-css "~5.3.2" + commander "^10.0.0" + entities "^4.4.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.15.1" + +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + +html-webpack-plugin@^5.6.0: + version "5.6.3" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz#a31145f0fee4184d53a794f9513147df1e653685" + integrity sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +htmlparser2@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010" + integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + domutils "^3.0.1" + entities "^4.3.0" + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.3" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== + +http-proxy-middleware@^2.0.9: + version "2.0.9" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" + integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http2-wrapper@^2.1.10: + version "2.2.1" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" + integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +hyperdyperid@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" + integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +image-size@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-2.0.2.tgz#84a7b43704db5736f364bf0d1b029821299b4bdc" + integrity sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w== + +immediate@^3.2.3: + version "3.3.0" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" + integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== + +import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infima@0.2.0-alpha.45: + version "0.2.0-alpha.45" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.45.tgz#542aab5a249274d81679631b492973dd2c1e7466" + integrity sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + +inline-style-parser@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22" + integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-core-module@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" + integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-nan@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-network-error@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.3.0.tgz#2ce62cbca444abd506f8a900f39d20b898d37512" + integrity sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw== + +is-npm@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.0.0.tgz#b59e75e8915543ca5d881ecff864077cba095261" + integrity sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-obj@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.0.0.tgz#06c0999fd7574edf5a906ba5644ad0feb3a84d22" + integrity sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-reference@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.0.tgz#b1380c03d96ddf7089709781e3208fceb0c92cd6" + integrity sha512-Eo1W3wUoHWoCoVM4GVl/a+K0IgiqE5aIo4kJABFyMum1ZORlPkC+UC357sSQUL5w5QCE5kCC9upl75b7+7CY/Q== + dependencies: + "@types/estree" "*" + +is-regex@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.3, is-typed-array@^1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.7.tgz#881ddc660b13cb8423b2090fa88c0fe37a83eb2f" + integrity sha512-VxlpTBGknhQ3o7YiVjIhdLU6+oD8dPz/79vvvH4F+S/c8608UCVa9fgDpa1kZgFoUST2DCgacc70UszKgzKuvA== + dependencies: + available-typed-arrays "^1.0.4" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-wsl@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" + +is-yarn-global@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.4.1.tgz#b312d902b313f81e4eaf98b6361ba2b45cd694bb" + integrity sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.4.3: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jiti@^1.20.0: + version "1.21.7" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== + +joi@^17.9.2: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@^3.0.2, jsesc@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json5@^2.1.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +katex@^0.16.9: + version "0.16.21" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.21.tgz#8f63c659e931b210139691f2cc7bb35166b792a3" + integrity sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A== + dependencies: + commander "^8.3.0" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +khroma@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.0.0.tgz#7577de98aed9f36c7a474c4d453d94c0d6c6588b" + integrity sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kleur@^4.0.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d" + integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== + +latest-version@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-7.0.0.tgz#843201591ea81a4d404932eeb61240fe04e9e5da" + integrity sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg== + dependencies: + package-json "^8.1.0" + +launch-editor@^2.6.1: + version "2.12.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.12.0.tgz#cc740f4e0263a6b62ead2485f9896e545321f817" + integrity sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg== + dependencies: + picocolors "^1.1.1" + shell-quote "^1.8.3" + +layout-base@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-1.0.2.tgz#1291e296883c322a9dd4c5dd82063721b53e26e2" + integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lilconfig@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + +loader-utils@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" + integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a" + integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== + dependencies: + p-locate "^6.0.0" + +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.escape@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@4.17.21, lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +longest-streak@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.0.1.tgz#c97315b7afa0e7d9525db9a5a2953651432bdc5d" + integrity sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lunr-languages@^1.4.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/lunr-languages/-/lunr-languages-1.14.0.tgz#6e97635f434631729dd0e5654daedd291cd6f2d0" + integrity sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA== + +lunr@^2.3.8: + version "2.3.9" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + +magic-string@^0.25.0, magic-string@^0.25.1: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + +markdown-extensions@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3" + integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q== + +markdown-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4" + integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== + +markdown-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" + integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== + dependencies: + repeat-string "^1.0.0" + +markdown-table@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c" + integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA== + +marked@^16.3.0: + version "16.4.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-16.4.1.tgz#db37c878cfa28fa57b8dd471fe92a83282911052" + integrity sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mdast-util-definitions@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817" + integrity sha512-5hcR7FL2EuZ4q6lLMUK5w4lHT2H3vqL9quPvYZ/Ku5iifrirfMHiGdhxdXMUbUkDmz5I+TYMd7nbaxUhbQkfpQ== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + unist-util-visit "^3.0.0" + +mdast-util-directive@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz#f3656f4aab6ae3767d3c72cfab5e8055572ccba1" + integrity sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-find-and-replace@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.1.0.tgz#69728acd250749f8aac6e150e07d1fd15619e829" + integrity sha512-1w1jbqAd13oU78QPBf5223+xB+37ecNtQ1JElq2feWols5oEYAl+SgNDnOZipe7NfLemoEt362yUS15/wip4mw== + dependencies: + escape-string-regexp "^5.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^4.0.0" + +mdast-util-find-and-replace@^3.0.0, mdast-util-find-and-replace@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== + dependencies: + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-from-markdown@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz#84df2924ccc6c995dec1e2368b2b208ad0a76268" + integrity sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + decode-named-character-reference "^1.0.0" + mdast-util-to-string "^3.1.0" + micromark "^3.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-decode-string "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-stringify-position "^3.0.0" + uvu "^0.5.0" + +mdast-util-from-markdown@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0" + integrity sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + decode-named-character-reference "^1.0.0" + mdast-util-to-string "^3.1.0" + micromark "^3.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-decode-string "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-stringify-position "^3.0.0" + uvu "^0.5.0" + +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-frontmatter@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz#f5f929eb1eb36c8a7737475c7eb438261f964ee8" + integrity sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + escape-string-regexp "^5.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-extension-frontmatter "^2.0.0" + +mdast-util-gfm-autolink-literal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz#4032dcbaddaef7d4f2f3768ed830475bb22d3970" + integrity sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg== + dependencies: + "@types/mdast" "^3.0.0" + ccount "^2.0.0" + mdast-util-find-and-replace "^2.0.0" + micromark-util-character "^1.0.0" + +mdast-util-gfm-autolink-literal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5" + integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== + dependencies: + "@types/mdast" "^4.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-find-and-replace "^3.0.0" + micromark-util-character "^2.0.0" + +mdast-util-gfm-footnote@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz#11d2d40a1a673a399c459e467fa85e00223191fe" + integrity sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-markdown "^1.3.0" + micromark-util-normalize-identifier "^1.0.0" + +mdast-util-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz#7778e9d9ca3df7238cc2bd3fa2b1bf6a65b19403" + integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + +mdast-util-gfm-strikethrough@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.1.tgz#a4a74c36864ec6a6e3bbd31e1977f29beb475789" + integrity sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-markdown "^1.3.0" + +mdast-util-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16" + integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-table@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.3.tgz#5f880aa6ecd1a9307cd7127f3d94c631ea88da07" + integrity sha512-B/tgpJjND1qIZM2WZst+NYnb0notPE6m0J+YOe3NOHXyEmvK38ytxaOsgz4BvrRPQQcNbRrTzSHMPnBkj1fCjg== + dependencies: + markdown-table "^3.0.0" + mdast-util-to-markdown "^1.3.0" + +mdast-util-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38" + integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-task-list-item@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz#6f35f09c6e2bcbe88af62fdea02ac199cc802c5c" + integrity sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-markdown "^1.3.0" + +mdast-util-gfm-task-list-item@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936" + integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-2.0.0.tgz#2545856bc18a66d5cc63fbef0b097a020a8e9e3d" + integrity sha512-wMwejlTN3EQADPFuvxe8lmGsay3+f6gSJKdAHR6KBJzpcxvsjJSILB9K6u6G7eQLC7iOTyVIHYGui9uBc9r1Tg== + dependencies: + mdast-util-gfm-autolink-literal "^1.0.0" + mdast-util-gfm-footnote "^1.0.0" + mdast-util-gfm-strikethrough "^1.0.0" + mdast-util-gfm-table "^1.0.0" + mdast-util-gfm-task-list-item "^1.0.0" + +mdast-util-gfm@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz#2cdf63b92c2a331406b0fb0db4c077c1b0331751" + integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-gfm-autolink-literal "^2.0.0" + mdast-util-gfm-footnote "^2.0.0" + mdast-util-gfm-strikethrough "^2.0.0" + mdast-util-gfm-table "^2.0.0" + mdast-util-gfm-task-list-item "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-expression@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.2.0.tgz#3e927afe27943956dc5d1c64cb949652062f71ff" + integrity sha512-wb36oi09XxqO9RVqgfD+xo8a7xaNgS+01+k3v0GKW0X0bYbeBmUZz22Z/IJ8SuphVlG+DNgNo9VoEaUJ3PKfJQ== + dependencies: + "@types/estree-jsx" "^0.0.1" + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + mdast-util-to-markdown "^1.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-2.0.1.tgz#03d003c8b0b4bd94ab092d876c0f92d2b0c83b0b" + integrity sha512-oPC7/smPBf7vxnvIYH5y3fPo2lw1rdrswFfSb4i0GTAXRUQv7JUU/t/hbp07dgGdUFTSDOHm5DNamhNg/s2Hrg== + dependencies: + "@types/estree-jsx" "^0.0.1" + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + ccount "^2.0.0" + mdast-util-to-markdown "^1.3.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-remove-position "^4.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-2.0.0.tgz#dd4f6c993cf27da32725e50a04874f595b7b63fb" + integrity sha512-M09lW0CcBT1VrJUaF/PYxemxxHa7SLDHdSn94Q9FhxjCQfuW7nMAWKWimTmA3OyDMSTH981NN1csW1X+HPSluw== + dependencies: + mdast-util-mdx-expression "^1.0.0" + mdast-util-mdx-jsx "^2.0.0" + mdast-util-mdxjs-esm "^1.0.0" + +mdast-util-mdx@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz#792f9cf0361b46bee1fdf1ef36beac424a099c41" + integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdxjs-esm@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-1.2.0.tgz#eca8b985f091c2d65a72c19d2740cefbc209aa63" + integrity sha512-IPpX9GBzAIbIRCjbyeLDpMhACFb0wxTIujuR3YElB8LWbducUdMgRJuqs/Vg8xQ1bIAMm7lw8L+YNtua0xKXRw== + dependencies: + "@types/estree-jsx" "^0.0.1" + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + mdast-util-to-markdown "^1.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^12.1.0: + version "12.1.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.1.1.tgz#89a2bb405eaf3b05eb8bf45157678f35eef5dbca" + integrity sha512-qE09zD6ylVP14jV4mjLIhDBOrpFdShHZcEsYvvKGABlr9mGbV7mTlRWdoFxL/EYSTNDiC9GZXy7y8Shgb9Dtzw== + dependencies: + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + "@types/mdurl" "^1.0.0" + mdast-util-definitions "^5.0.0" + mdurl "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + unist-builder "^3.0.0" + unist-util-generated "^2.0.0" + unist-util-position "^4.0.0" + unist-util-visit "^4.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz#38b6cdc8dc417de642a469c4fc2abdf8c931bd1e" + integrity sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + longest-streak "^3.0.0" + mdast-util-to-string "^3.0.0" + micromark-util-decode-string "^1.0.0" + unist-util-visit "^4.0.0" + zwitch "^2.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz#56c506d065fbf769515235e577b5a261552d56e9" + integrity sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA== + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + +mdurl@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memfs@^4.43.1: + version "4.50.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.50.0.tgz#1832177d5592ec1e6a816fb4fe01012ada2856e7" + integrity sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA== + dependencies: + "@jsonjoy.com/json-pack" "^1.11.0" + "@jsonjoy.com/util" "^1.9.0" + glob-to-regex.js "^1.0.1" + thingies "^2.5.0" + tree-dump "^1.0.3" + tslib "^2.0.0" + +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +mermaid@^10.9.0: + version "10.9.3" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.9.3.tgz#90bc6f15c33dbe5d9507fed31592cc0d88fee9f7" + integrity sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw== + dependencies: + "@braintree/sanitize-url" "^6.0.1" + "@types/d3-scale" "^4.0.3" + "@types/d3-scale-chromatic" "^3.0.0" + cytoscape "^3.28.1" + cytoscape-cose-bilkent "^4.1.0" + d3 "^7.4.0" + d3-sankey "^0.12.3" + dagre-d3-es "7.0.10" + dayjs "^1.11.7" + dompurify "^3.0.5 <3.1.7" + elkjs "^0.9.0" + katex "^0.16.9" + khroma "^2.0.0" + lodash-es "^4.17.21" + mdast-util-from-markdown "^1.3.0" + non-layered-tidy-tree-layout "^2.0.2" + stylis "^4.1.3" + ts-dedent "^2.2.0" + uuid "^9.0.0" + web-worker "^1.2.0" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz#edff4c72e5993d93724a3c206970f5a15b0585ad" + integrity sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-factory-destination "^1.0.0" + micromark-factory-label "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-factory-title "^1.0.0" + micromark-factory-whitespace "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-classify-character "^1.0.0" + micromark-util-html-tag-name "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + uvu "^0.5.0" + +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-directive@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz#2eb61985d1995a7c1ff7621676a4f32af29409e8" + integrity sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + parse-entities "^4.0.0" + +micromark-extension-frontmatter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz#651c52ffa5d7a8eeed687c513cd869885882d67a" + integrity sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg== + dependencies: + fault "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-autolink-literal@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz#dc589f9c37eaff31a175bab49f12290edcf96058" + integrity sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-autolink-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-footnote@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.3.tgz#5280b29667e4ecb8687f369829aa3322caca7d11" + integrity sha512-bn62pC5y39rIo2g1RqZk1NhF7T7cJLuJlbevunQz41U0iPVCdVOFASe5/L1kke+DFKSgfCRhv24+o42cZ1+ADw== + dependencies: + micromark-core-commonmark "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-symbol "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-strikethrough@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz#162232c284ffbedd8c74e59c1525bda217295e18" + integrity sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-classify-character "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-strikethrough@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923" + integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-table@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz#7b708b728f8dc4d95d486b9e7a2262f9cddbcbb4" + integrity sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-table@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b" + integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-tagfilter@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz#fb2e303f7daf616db428bb6a26e18fda14a90a4d" + integrity sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA== + dependencies: + micromark-util-types "^1.0.0" + +micromark-extension-gfm-tagfilter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57" + integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-gfm-task-list-item@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz#7683641df5d4a09795f353574d7f7f66e47b7fc4" + integrity sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-task-list-item@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c" + integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz#40f3209216127a96297c54c67f5edc7ef2d1a2a2" + integrity sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA== + dependencies: + micromark-extension-gfm-autolink-literal "^1.0.0" + micromark-extension-gfm-footnote "^1.0.0" + micromark-extension-gfm-strikethrough "^1.0.0" + micromark-extension-gfm-table "^1.0.0" + micromark-extension-gfm-tagfilter "^1.0.0" + micromark-extension-gfm-task-list-item "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-extension-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b" + integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== + dependencies: + micromark-extension-gfm-autolink-literal "^2.0.0" + micromark-extension-gfm-footnote "^2.0.0" + micromark-extension-gfm-strikethrough "^2.0.0" + micromark-extension-gfm-table "^2.0.0" + micromark-extension-gfm-tagfilter "^2.0.0" + micromark-extension-gfm-task-list-item "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-expression@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.3.tgz#cd3843573921bf55afcfff4ae0cd2e857a16dcfa" + integrity sha512-TjYtjEMszWze51NJCZmhv7MEBcgYRgb3tJeMAJ+HQCAaZHHRBaDCccqQzGizR/H4ODefP44wRTgOn2vE5I6nZA== + dependencies: + micromark-factory-mdx-expression "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-events-to-acorn "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-mdx-expression@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz#1407b9ce69916cf5e03a196ad9586889df25302a" + integrity sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-jsx@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-1.0.3.tgz#9f196be5f65eb09d2a49b237a7b3398bba2999be" + integrity sha512-VfA369RdqUISF0qGgv2FfV7gGjHDfn9+Qfiv5hEwpyr1xscRj/CiVRkU7rywGFCO7JwJ5L0e7CJz60lY52+qOA== + dependencies: + "@types/acorn" "^4.0.0" + estree-util-is-identifier-name "^2.0.0" + micromark-factory-mdx-expression "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + vfile-message "^3.0.0" + +micromark-extension-mdx-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz#5abb83da5ddc8e473a374453e6ea56fbd66b59ad" + integrity sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdx-md@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-1.0.0.tgz#382f5df9ee3706dd120b51782a211f31f4760d22" + integrity sha512-xaRAMoSkKdqZXDAoSgp20Azm0aRQKGOl0RrS81yGu8Hr/JhMsBmfs4wR7m9kgVUIO36cMUQjNyiyDKPrsv8gOw== + dependencies: + micromark-util-types "^1.0.0" + +micromark-extension-mdx-md@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz#1d252881ea35d74698423ab44917e1f5b197b92d" + integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-mdxjs-esm@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-1.0.2.tgz#df0c48743a0b1988119489c68314160b7942ffa6" + integrity sha512-bIaxblNIM+CCaJvp3L/V+168l79iuNmxEiTU6i3vB0YuDW+rumV64BFMxvhfRDxaJxQE1zD5vTPdyLBbW4efGA== + dependencies: + micromark-core-commonmark "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-events-to-acorn "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-position-from-estree "^1.1.0" + uvu "^0.5.0" + vfile-message "^3.0.0" + +micromark-extension-mdxjs-esm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz#de21b2b045fd2059bd00d36746081de38390d54a" + integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdxjs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-1.0.0.tgz#772644e12fc8299a33e50f59c5aa15727f6689dd" + integrity sha512-TZZRZgeHvtgm+IhtgC2+uDMR7h8eTKF0QUX9YsgoL9+bADBpBY6SiLvWqnBlLbCEevITmTqmEuY3FoxMKVs1rQ== + dependencies: + acorn "^8.0.0" + acorn-jsx "^5.0.0" + micromark-extension-mdx-expression "^1.0.0" + micromark-extension-mdx-jsx "^1.0.0" + micromark-extension-mdx-md "^1.0.0" + micromark-extension-mdxjs-esm "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-extension-mdxjs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz#b5a2e0ed449288f3f6f6c544358159557549de18" + integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ== + dependencies: + acorn "^8.0.0" + acorn-jsx "^5.0.0" + micromark-extension-mdx-expression "^3.0.0" + micromark-extension-mdx-jsx "^3.0.0" + micromark-extension-mdx-md "^2.0.0" + micromark-extension-mdxjs-esm "^3.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz#fef1cb59ad4997c496f887b6977aa3034a5a277e" + integrity sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz#6be2551fa8d13542fcbbac478258fb7a20047137" + integrity sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-mdx-expression@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-1.0.6.tgz#917e17d16e6e9c2551f3a862e6a9ebdd22056476" + integrity sha512-WRQIc78FV7KrCfjsEf/sETopbYjElh3xAmNpLkd1ODPqxEngP42eVRGbiPEQWpRV27LzqW+XVTvQAMIIRLPnNA== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-events-to-acorn "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-position-from-estree "^1.0.0" + uvu "^0.5.0" + vfile-message "^3.0.0" + +micromark-factory-mdx-expression@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz#2afaa8ba6d5f63e0cead3e4dee643cad184ca260" + integrity sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-factory-space@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz#cebff49968f2b9616c0fcb239e96685cb9497633" + integrity sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz#7e09287c3748ff1693930f176e1c4a328382494f" + integrity sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz#e991e043ad376c1ba52f4e49858ce0794678621c" + integrity sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.1.0.tgz#d97c54d5742a0d9611a68ca0cd4124331f264d86" + integrity sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg== + dependencies: + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-character@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz#4fedaa3646db249bc58caeb000eb3549a8ca5dcc" + integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg== + dependencies: + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06" + integrity sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz#cbd7b447cb79ee6997dd274a46fc4eb806460a20" + integrity sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz#91418e1e74fb893e3628b8d496085639124ff3d5" + integrity sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz#dcc85f13b5bd93ff8d2868c3dba28039d490b946" + integrity sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz#942252ab7a76dec2dbf089cc32505ee2bc3acf02" + integrity sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz#2c1c22d3800870ad770ece5686ebca5920353383" + integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA== + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-events-to-acorn@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.0.4.tgz#07d26cd675dbca8c38b8d9aff2d4cdc91c9997aa" + integrity sha512-dpo8ecREK5s/KMph7jJ46RLM6g7N21CMc9LAJQbDLdbQnTpijigkSJPTIfLXZ+h5wdXlcsQ+b6ufAE9v76AdgA== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^0.0.50" + estree-util-visit "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + vfile-message "^3.0.0" + +micromark-util-events-to-acorn@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz#4275834f5453c088bd29cd72dfbf80e3327cec07" + integrity sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-util-html-tag-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.0.0.tgz#75737e92fef50af0c6212bd309bc5cb8dbd489ed" + integrity sha512-NenEKIshW2ZI/ERv9HtFNsrn3llSPZtY337LID/24WeLqMzeZhBEE6BQ0vS2ZBjshm5n40chKtJ3qjAbVV8S0g== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz#4a3539cb8db954bbec5203952bfe8cedadae7828" + integrity sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz#a7c363f49a0162e931960c44f3127ab58f031d88" + integrity sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw== + dependencies: + micromark-util-types "^1.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz#27dc875397cd15102274c6c6da5585d34d4f12b2" + integrity sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105" + integrity sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz#b90344db62042ce454f351cf0bebcc0a6da4920e" + integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ== + +micromark-util-symbol@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142" + integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag== + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20" + integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w== + +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +micromark@^3.0.0: + version "3.0.10" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.0.10.tgz#1eac156f0399d42736458a14b0ca2d86190b457c" + integrity sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + micromark-core-commonmark "^1.0.1" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + uvu "^0.5.0" + +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromatch@^4.0.2, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mime-db@1.49.0, "mime-db@>= 1.43.0 < 2": + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + +mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24: + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== + dependencies: + mime-db "1.49.0" + +mime-types@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" + integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + dependencies: + mime-db "^1.54.0" + +mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +mimic-response@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" + integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== + +mini-css-extract-plugin@^2.9.2: + version "2.9.4" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz#cafa1a42f8c71357f49cd1566810d74ff1cb0200" + integrity sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ== + dependencies: + schema-utils "^4.0.0" + tapable "^2.2.1" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@3.1.2, minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" + integrity sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew== + +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + +mrmime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.1.tgz#bc3e87f7987853a54c9850eeb1f1078cd44adddc" + integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +nanoid@^3.3.8: + version "3.3.10" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.10.tgz#7bc882237698ef787d5cbba109e3b0168ba6e7b1" + integrity sha512-vSJJTG+t/dIKAUhUDw/dLdZ9s//5OxcHqLaDWWrW4Cdq7o6tdLIczUkMXt2MBNmk6sJRZBZRXVixs7URY1CmIg== + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nmtree@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/nmtree/-/nmtree-1.0.6.tgz#953e057ad545e9e627f1275bd25fea4e92c1cf63" + integrity sha512-SUPCoyX5w/lOT6wD/PZEymR+J899984tYEOYjuDqQlIOeX5NSb1MEsCcT0az+dhZD0MLAj5hGBZEpKQxuDdniA== + dependencies: + commander "^2.11.0" + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-emoji@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.2.0.tgz#1d000e3c76e462577895be1b436f4aa2d6760eb0" + integrity sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw== + dependencies: + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" + +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^1.1.75: + version "1.1.75" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" + integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +node-releases@^2.0.26, node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== + +non-layered-tidy-tree-layout@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804" + integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw== + +nopt@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.1.tgz#9b7d96af9836577c58f5883e939365fa15623a4a" + integrity sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w== + +not@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/not/-/not-0.1.0.tgz#c9691c1746c55dcfbe54cbd8bd4ff041bc2b519d" + integrity sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nprogress@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" + integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E= + +nth-check@^2.0.0, nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +null-loader@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-4.0.1.tgz#8e63bd3a2dd3c64236a4679428632edd0a6dbc6a" + integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1, on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^10.0.3: + version "10.2.0" + resolved "https://registry.yarnpkg.com/open/-/open-10.2.0.tgz#b9d855be007620e80b6fb05fac98141fe62db73c" + integrity sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA== + dependencies: + default-browser "^5.2.1" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + wsl-utils "^0.1.0" + +open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +os-homedir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +p-cancelable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" + integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" + integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== + dependencies: + yocto-queue "^1.0.0" + +p-locate@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f" + integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== + dependencies: + p-limit "^4.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-retry@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.1.tgz#81828f8dc61c6ef5a800585491572cc9892703af" + integrity sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ== + dependencies: + "@types/retry" "0.12.2" + is-network-error "^1.0.0" + retry "^0.13.1" + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +package-json@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8" + integrity sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA== + dependencies: + got "^12.1.0" + registry-auth-token "^5.0.1" + registry-url "^6.0.0" + semver "^7.3.7" + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.0.tgz#f67c856d4e3fe19b1a445c3fabe78dcdc1053eeb" + integrity sha512-5nk9Fn03x3rEhGaX1FU6IDwG/k+GxLXlFAkgrbM1asuAFl3BhdQWvASaIsmwWypRNcZKHPYnIuOSfIWEyEQnPQ== + dependencies: + "@types/unist" "^2.0.0" + character-entities "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parse5@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.0.0.tgz#51f74a5257f5fcc536389e8c2d0b3802e1bfa91a" + integrity sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g== + dependencies: + entities "^4.3.0" + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-exists@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" + integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== + +path-is-inside@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= + dependencies: + path-root-regex "^0.1.0" + +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== + +path-to-regexp@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" + integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +periscopic@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.0.4.tgz#b3fbed0d1bc844976b977173ca2cd4a0ef4fa8d1" + integrity sha512-SFx68DxCv0Iyo6APZuw/AKewkkThGwssmU0QWtTlvov3VAtPX+QJ4CadwSaz8nrT5jPIuxdvJWB4PnD2KNDxQg== + dependencies: + estree-walker "^3.0.0" + is-reference "^3.0.0" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-7.0.0.tgz#8f0c08d6df4476756c5ff29b3282d0bab7517d11" + integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA== + dependencies: + find-up "^6.3.0" + +postcss-attribute-case-insensitive@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz#0c4500e3bcb2141848e89382c05b5a31c23033a3" + integrity sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-calc@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== + dependencies: + postcss-selector-parser "^6.0.11" + postcss-value-parser "^4.2.0" + +postcss-clamp@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" + integrity sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-functional-notation@^7.0.12: + version "7.0.12" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz#9a3df2296889e629fde18b873bb1f50a4ecf4b83" + integrity sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +postcss-color-hex-alpha@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz#5dd3eba1f8facb4ea306cba6e3f7712e876b0c76" + integrity sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-color-rebeccapurple@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz#5ada28406ac47e0796dff4056b0a9d5a6ecead98" + integrity sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-colormin@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz#076e8d3fb291fbff7b10e6b063be9da42ff6488d" + integrity sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + colord "^2.9.3" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz#3498387f8efedb817cbc63901d45bd1ceaa40f48" + integrity sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w== + dependencies: + browserslist "^4.23.0" + postcss-value-parser "^4.2.0" + +postcss-custom-media@^11.0.6: + version "11.0.6" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz#6b450e5bfa209efb736830066682e6567bd04967" + integrity sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" + +postcss-custom-properties@^14.0.6: + version "14.0.6" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz#1af73a650bf115ba052cf915287c9982825fc90e" + integrity sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-custom-selectors@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz#9448ed37a12271d7ab6cb364b6f76a46a4a323e8" + integrity sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + postcss-selector-parser "^7.0.0" + +postcss-dir-pseudo-class@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz#80d9e842c9ae9d29f6bf5fd3cf9972891d6cc0ca" + integrity sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-discard-comments@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz#e768dcfdc33e0216380623652b0a4f69f4678b6c" + integrity sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw== + +postcss-discard-duplicates@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz#d121e893c38dc58a67277f75bb58ba43fce4c3eb" + integrity sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw== + +postcss-discard-empty@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz#ee39c327219bb70473a066f772621f81435a79d9" + integrity sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ== + +postcss-discard-overridden@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz#4e9f9c62ecd2df46e8fdb44dc17e189776572e2d" + integrity sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ== + +postcss-discard-unused@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz#c1b0e8c032c6054c3fbd22aaddba5b248136f338" + integrity sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA== + dependencies: + postcss-selector-parser "^6.0.16" + +postcss-double-position-gradients@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz#b482d08b5ced092b393eb297d07976ab482d4cad" + integrity sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-focus-visible@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz#1f7904904368a2d1180b220595d77b6f8a957868" + integrity sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-focus-within@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz#ac01ce80d3f2e8b2b3eac4ff84f8e15cd0057bc7" + integrity sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-font-variant@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" + integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== + +postcss-gap-properties@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz#d5ff0bdf923c06686499ed2b12e125fe64054fed" + integrity sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw== + +postcss-image-set-function@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz#538e94e16716be47f9df0573b56bbaca86e1da53" + integrity sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-lab-function@^7.0.12: + version "7.0.12" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz#eb555ac542607730eb0a87555074e4a5c6eef6e4" + integrity sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +postcss-loader@^7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.4.tgz#aed9b79ce4ed7e9e89e56199d25ad1ec8f606209" + integrity sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A== + dependencies: + cosmiconfig "^8.3.5" + jiti "^1.20.0" + semver "^7.5.4" + +postcss-logical@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-8.1.0.tgz#4092b16b49e3ecda70c4d8945257da403d167228" + integrity sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-merge-idents@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz#7b9c31c7bc823c94bec50f297f04e3c2b838ea65" + integrity sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g== + dependencies: + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-merge-longhand@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz#ba8a8d473617c34a36abbea8dda2b215750a065a" + integrity sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^6.1.1" + +postcss-merge-rules@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz#7aa539dceddab56019469c0edd7d22b64c3dea9d" + integrity sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + cssnano-utils "^4.0.2" + postcss-selector-parser "^6.0.16" + +postcss-minify-font-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz#a0e574c02ee3f299be2846369211f3b957ea4c59" + integrity sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz#ca3eb55a7bdb48a1e187a55c6377be918743dbd6" + integrity sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q== + dependencies: + colord "^2.9.3" + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz#54551dec77b9a45a29c3cb5953bf7325a399ba08" + integrity sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA== + dependencies: + browserslist "^4.23.0" + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz#197f7d72e6dd19eed47916d575d69dc38b396aff" + integrity sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ== + dependencies: + postcss-selector-parser "^6.0.16" + +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== + +postcss-modules-local-by-default@^4.0.5: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz#d150f43837831dae25e4085596e84f6f5d6ec368" + integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c" + integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-nesting@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-13.0.2.tgz#fde0d4df772b76d03b52eccc84372e8d1ca1402e" + integrity sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ== + dependencies: + "@csstools/selector-resolve-nested" "^3.1.0" + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + +postcss-normalize-charset@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz#1ec25c435057a8001dac942942a95ffe66f721e1" + integrity sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ== + +postcss-normalize-display-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz#54f02764fed0b288d5363cbb140d6950dbbdd535" + integrity sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz#e982d284ec878b9b819796266f640852dbbb723a" + integrity sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz#f8006942fd0617c73f049dd8b6201c3a3040ecf3" + integrity sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz#e3cc6ad5c95581acd1fc8774b309dd7c06e5e363" + integrity sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz#40cb8726cef999de984527cbd9d1db1f3e9062c0" + integrity sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz#aaf8bbd34c306e230777e80f7f12a4b7d27ce06e" + integrity sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg== + dependencies: + browserslist "^4.23.0" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz#292792386be51a8de9a454cb7b5c58ae22db0f79" + integrity sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz#fbb009e6ebd312f8b2efb225c2fcc7cf32b400cd" + integrity sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-opacity-percentage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz#0b0db5ed5db5670e067044b8030b89c216e1eb0a" + integrity sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ== + +postcss-ordered-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz#366bb663919707093451ab70c3f99c05672aaae5" + integrity sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q== + dependencies: + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-overflow-shorthand@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz#f5252b4a2ee16c68cd8a9029edb5370c4a9808af" + integrity sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-page-break@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" + integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== + +postcss-place@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-10.0.0.tgz#ba36ee4786ca401377ced17a39d9050ed772e5a9" + integrity sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-preset-env@^10.2.1: + version "10.4.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz#fa6167a307f337b2bcdd1d125604ff97cdeb5142" + integrity sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw== + dependencies: + "@csstools/postcss-alpha-function" "^1.0.1" + "@csstools/postcss-cascade-layers" "^5.0.2" + "@csstools/postcss-color-function" "^4.0.12" + "@csstools/postcss-color-function-display-p3-linear" "^1.0.1" + "@csstools/postcss-color-mix-function" "^3.0.12" + "@csstools/postcss-color-mix-variadic-function-arguments" "^1.0.2" + "@csstools/postcss-content-alt-text" "^2.0.8" + "@csstools/postcss-contrast-color-function" "^2.0.12" + "@csstools/postcss-exponential-functions" "^2.0.9" + "@csstools/postcss-font-format-keywords" "^4.0.0" + "@csstools/postcss-gamut-mapping" "^2.0.11" + "@csstools/postcss-gradients-interpolation-method" "^5.0.12" + "@csstools/postcss-hwb-function" "^4.0.12" + "@csstools/postcss-ic-unit" "^4.0.4" + "@csstools/postcss-initial" "^2.0.1" + "@csstools/postcss-is-pseudo-class" "^5.0.3" + "@csstools/postcss-light-dark-function" "^2.0.11" + "@csstools/postcss-logical-float-and-clear" "^3.0.0" + "@csstools/postcss-logical-overflow" "^2.0.0" + "@csstools/postcss-logical-overscroll-behavior" "^2.0.0" + "@csstools/postcss-logical-resize" "^3.0.0" + "@csstools/postcss-logical-viewport-units" "^3.0.4" + "@csstools/postcss-media-minmax" "^2.0.9" + "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.5" + "@csstools/postcss-nested-calc" "^4.0.0" + "@csstools/postcss-normalize-display-values" "^4.0.0" + "@csstools/postcss-oklab-function" "^4.0.12" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/postcss-random-function" "^2.0.1" + "@csstools/postcss-relative-color-syntax" "^3.0.12" + "@csstools/postcss-scope-pseudo-class" "^4.0.1" + "@csstools/postcss-sign-functions" "^1.1.4" + "@csstools/postcss-stepped-value-functions" "^4.0.9" + "@csstools/postcss-text-decoration-shorthand" "^4.0.3" + "@csstools/postcss-trigonometric-functions" "^4.0.9" + "@csstools/postcss-unset-value" "^4.0.0" + autoprefixer "^10.4.21" + browserslist "^4.26.0" + css-blank-pseudo "^7.0.1" + css-has-pseudo "^7.0.3" + css-prefers-color-scheme "^10.0.0" + cssdb "^8.4.2" + postcss-attribute-case-insensitive "^7.0.1" + postcss-clamp "^4.1.0" + postcss-color-functional-notation "^7.0.12" + postcss-color-hex-alpha "^10.0.0" + postcss-color-rebeccapurple "^10.0.0" + postcss-custom-media "^11.0.6" + postcss-custom-properties "^14.0.6" + postcss-custom-selectors "^8.0.5" + postcss-dir-pseudo-class "^9.0.1" + postcss-double-position-gradients "^6.0.4" + postcss-focus-visible "^10.0.1" + postcss-focus-within "^9.0.1" + postcss-font-variant "^5.0.0" + postcss-gap-properties "^6.0.0" + postcss-image-set-function "^7.0.0" + postcss-lab-function "^7.0.12" + postcss-logical "^8.1.0" + postcss-nesting "^13.0.2" + postcss-opacity-percentage "^3.0.0" + postcss-overflow-shorthand "^6.0.0" + postcss-page-break "^3.0.4" + postcss-place "^10.0.0" + postcss-pseudo-class-any-link "^10.0.1" + postcss-replace-overflow-wrap "^4.0.0" + postcss-selector-not "^8.0.1" + +postcss-pseudo-class-any-link@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz#06455431171bf44b84d79ebaeee9fd1c05946544" + integrity sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-reduce-idents@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz#b0d9c84316d2a547714ebab523ec7d13704cd486" + integrity sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz#4401297d8e35cb6e92c8e9586963e267105586ba" + integrity sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz#6fa2c586bdc091a7373caeee4be75a0f3e12965d" + integrity sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-replace-overflow-wrap@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" + integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== + +postcss-selector-not@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz#f2df9c6ac9f95e9fe4416ca41a957eda16130172" + integrity sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.16: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-selector-parser@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262" + integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-sort-media-queries@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz#4556b3f982ef27d3bac526b99b6c0d3359a6cf97" + integrity sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA== + dependencies: + sort-css-media-queries "2.2.0" + +postcss-svgo@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz#1d6e180d6df1fa8a3b30b729aaa9161e94f04eaa" + integrity sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^3.2.0" + +postcss-unique-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz#983ab308896b4bf3f2baaf2336e14e52c11a2088" + integrity sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg== + dependencies: + postcss-selector-parser "^6.0.16" + +postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss-zindex@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-6.0.2.tgz#e498304b83a8b165755f53db40e2ea65a99b56e1" + integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== + +postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.33: + version "8.5.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" + integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +postcss@^8.5.4: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +pretty-time@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" + integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== + +prism-react-renderer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.2.1.tgz#392460acf63540960e5e3caa699d851264e99b89" + integrity sha512-w23ch4f75V1Tnz8DajsYKvY5lF7H1+WvzvLUcF0paFxkTHSp42RS0H5CttdN2Q8RR3DRGZ9v5xD/h3n8C8kGmg== + +prism-react-renderer@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.3.tgz#9b5a4211a6756eee3c96fee9a05733abc0b0805c" + integrity sha512-Viur/7tBTCH2HmYzwCHmt2rEFn+rdIWNIINXyg0StiISbDiIhHKhrFuEK8eMkKgvsIYSjgGqy/hNyucHp6FpoQ== + +prism-react-renderer@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz#ac63b7f78e56c8f2b5e76e823a976d5ede77e35f" + integrity sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig== + dependencies: + "@types/prismjs" "^1.26.0" + clsx "^2.0.0" + +prismjs@^1.29.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9" + integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +property-information@^5.0.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + +property-information@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22" + integrity sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w== + +property-information@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.0.0.tgz#3508a6d6b0b8eb3ca6eb2c6623b164d2ed2ab112" + integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg== + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +pupa@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-3.1.0.tgz#f15610274376bbcc70c9a3aa8b505ea23f41c579" + integrity sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug== + dependencies: + escape-goat "^4.0.0" + +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-dom@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react-fast-compare@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + +"react-helmet-async@npm:@slorber/react-helmet-async@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz#11fbc6094605cf60aa04a28c17e0aab894b4ecff" + integrity sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A== + dependencies: + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" + prop-types "^15.7.2" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-json-view-lite@^2.3.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz#c7ff011c7cc80e9900abc7aa4916c6a5c6d6c1c6" + integrity sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g== + +react-lifecycles-compat@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-live@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-live/-/react-live-2.3.0.tgz#09fbac361903970e7cf51cee60729eeb164a5d87" + integrity sha512-b+Nc7x/bLu2sPX/If1uncrmUvYtXTqxY8QpzBw/X76SA3QJ1ggU0Ld6X5phLXZ469+XWO5lOU7OpAt0JoTyZPQ== + dependencies: + "@types/buble" "^0.20.0" + buble "0.19.6" + core-js "^3.14.0" + dom-iterator "^1.0.0" + prism-react-renderer "^1.2.1" + prop-types "^15.7.2" + react-simple-code-editor "^0.11.0" + unescape "^1.0.1" + +react-loadable-ssr-addon-v5-slorber@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" + integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== + dependencies: + "@babel/runtime" "^7.10.3" + +"react-loadable@npm:@docusaurus/react-loadable@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz#de6c7f73c96542bd70786b8e522d535d69069dc4" + integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ== + dependencies: + "@types/react" "*" + +react-modal@3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.15.1.tgz#950ce67bfef80971182dd0ed38f2d9b1a681288b" + integrity sha512-duB9bxOaYg7Zt6TMFldIFxQRtSP+Dg3F1ZX3FXxSUn+3tZZ/9JCgeAQKDg7rhZSAqopq8TFRw3yIbnx77gyFTw== + dependencies: + exenv "^1.2.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + +react-router-config@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" + integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== + dependencies: + "@babel/runtime" "^7.1.2" + +react-router-dom@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" + integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.3.4" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.3.4, react-router@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" + integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-simple-code-editor@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.11.0.tgz#bb57c7c29b570f2ab229872599eac184f5bc673c" + integrity sha512-xGfX7wAzspl113ocfKQAR8lWPhavGWHL3xSzNLeseDRHysT+jzRBi/ExdUqevSMos+7ZtdfeuBOXtgk9HTwsrw== + +react@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^2.0.1: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +recma-build-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz#c02f29e047e103d2fab2054954e1761b8ea253c4" + integrity sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew== + dependencies: + "@types/estree" "^1.0.0" + estree-util-build-jsx "^3.0.0" + vfile "^6.0.0" + +recma-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-jsx/-/recma-jsx-1.0.0.tgz#f7bef02e571a49d6ba3efdfda8e2efab48dbe3aa" + integrity sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q== + dependencies: + acorn-jsx "^5.0.0" + estree-util-to-js "^2.0.0" + recma-parse "^1.0.0" + recma-stringify "^1.0.0" + unified "^11.0.0" + +recma-parse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-parse/-/recma-parse-1.0.0.tgz#c351e161bb0ab47d86b92a98a9d891f9b6814b52" + integrity sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ== + dependencies: + "@types/estree" "^1.0.0" + esast-util-from-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +recma-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-stringify/-/recma-stringify-1.0.0.tgz#54632030631e0c7546136ff9ef8fde8e7b44f130" + integrity sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g== + dependencies: + "@types/estree" "^1.0.0" + estree-util-to-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== + dependencies: + regenerate "^1.4.2" + +regenerate-unicode-properties@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" + integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== + dependencies: + regenerate "^1.4.2" + +regenerate-unicode-properties@^10.2.2: + version "10.2.2" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz#aa113812ba899b630658c7623466be71e1f86f66" + integrity sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g== + dependencies: + regenerate "^1.4.2" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0, regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^4.2.0: + version "4.7.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" + integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regexpu-core@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" + integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + +regexpu-core@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" + integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.0" + regjsgen "^0.8.0" + regjsparser "^0.12.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regexpu-core@^6.3.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.4.0.tgz#3580ce0c4faedef599eccb146612436b62a176e5" + integrity sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.2" + regjsgen "^0.8.0" + regjsparser "^0.13.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.2.1" + +registry-auth-token@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.1.0.tgz#3c659047ecd4caebd25bc1570a3aa979ae490eca" + integrity sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw== + dependencies: + "@pnpm/npm-conf" "^2.1.0" + +registry-url@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-6.0.1.tgz#056d9343680f2f64400032b1e199faa692286c58" + integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q== + dependencies: + rc "1.2.8" + +regjsgen@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== + +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" + integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== + dependencies: + jsesc "~3.0.2" + +regjsparser@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" + integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== + dependencies: + jsesc "~3.1.0" + +regjsparser@^0.6.4: + version "0.6.9" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.9.tgz#b489eef7c9a2ce43727627011429cf833a7183e6" + integrity sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ== + dependencies: + jsesc "~0.5.0" + +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== + dependencies: + jsesc "~0.5.0" + +rehype-parse@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-7.0.1.tgz#58900f6702b56767814afc2a9efa2d42b1c90c57" + integrity sha512-fOiR9a9xH+Le19i4fGzIEowAbwG7idy2Jzs4mOrFWBSJ0sNUgy0ev871dwWnbOo371SjgjG4pwzrbgSVrKxecw== + dependencies: + hast-util-from-parse5 "^6.0.0" + parse5 "^6.0.0" + +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + +rehype-recma@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rehype-recma/-/rehype-recma-1.0.0.tgz#d68ef6344d05916bd96e25400c6261775411aa76" + integrity sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + hast-util-to-estree "^3.0.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + +remark-directive@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/remark-directive/-/remark-directive-3.0.1.tgz#689ba332f156cfe1118e849164cc81f157a3ef0a" + integrity sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-directive "^3.0.0" + micromark-extension-directive "^3.0.0" + unified "^11.0.0" + +remark-emoji@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-4.0.1.tgz#671bfda668047689e26b2078c7356540da299f04" + integrity sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg== + dependencies: + "@types/mdast" "^4.0.2" + emoticon "^4.0.1" + mdast-util-find-and-replace "^3.0.1" + node-emoji "^2.1.0" + unified "^11.0.4" + +remark-frontmatter@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz#b68d61552a421ec412c76f4f66c344627dc187a2" + integrity sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-frontmatter "^2.0.0" + micromark-extension-frontmatter "^2.0.0" + unified "^11.0.0" + +remark-gfm@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f" + integrity sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-gfm "^2.0.0" + micromark-extension-gfm "^2.0.0" + unified "^10.0.0" + +remark-gfm@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b" + integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-gfm "^3.0.0" + micromark-extension-gfm "^3.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" + +remark-mdx-filter-imports@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/remark-mdx-filter-imports/-/remark-mdx-filter-imports-0.1.3.tgz#52aeeb235a1ac43d22d40359ae7df2632596b00c" + integrity sha512-tReGHtQ9sFMqrp95pwsIaEB6qVtGCN5urnVnkVauYHk4OLsYL5EcCVw0CnqvvjPCS4IX1I6ikK8LKWraTj+JjQ== + dependencies: + "@babel/generator" "^7.12.5" + "@babel/parser" "^7.12.7" + unist-util-visit "^2.0.3" + +remark-mdx@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-2.1.1.tgz#14021be9ecbc9ad0310f4240980221328aa7ed55" + integrity sha512-0wXdEITnFyjLquN3VvACNLzbGzWM5ujzTvfgOkONBZgSFJ7ezLLDaTWqf6H9eUgVITEP8asp6LJ0W/X090dXBg== + dependencies: + mdast-util-mdx "^2.0.0" + micromark-extension-mdxjs "^1.0.0" + +remark-mdx@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-3.1.0.tgz#f979be729ecb35318fa48e2135c1169607a78343" + integrity sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA== + dependencies: + mdast-util-mdx "^3.0.0" + micromark-extension-mdxjs "^3.0.0" + +remark-parse@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-10.0.1.tgz#6f60ae53edbf0cf38ea223fe643db64d112e0775" + integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + unified "^10.0.0" + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-10.1.0.tgz#32dc99d2034c27ecaf2e0150d22a6dcccd9a6279" + integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw== + dependencies: + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-to-hast "^12.1.0" + unified "^10.0.0" + +remark-rehype@^11.0.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.1.tgz#f864dd2947889a11997c0a2667cd6b38f685bca7" + integrity sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +repeat-string@^1.0.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +"require-like@>= 0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" + integrity sha1-rW8wwTvs15cBDEaK+ndcDAprR/o= + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resolve-alpn@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-package-path@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-4.0.3.tgz#31dab6897236ea6613c72b83658d88898a9040aa" + integrity sha512-SRpNAPW4kewOaNUt8VPqhJ0UMxawMwzJD8V7m1cJfdSTK9ieZwS6K7Dabsm4bmLFM96Z5Y/UznrpG5kt1im8yA== + dependencies: + path-root "^0.1.1" + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve@^1.14.2: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +resolve@^1.22.10: + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== + dependencies: + is-core-module "^2.16.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" + integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg== + dependencies: + lowercase-keys "^3.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +robust-predicates@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" + integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== + +rtlcss@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-4.3.0.tgz#f8efd4d5b64f640ec4af8fa25b65bacd9e07cc97" + integrity sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + postcss "^8.4.21" + strip-json-comments "^3.1.1" + +run-applescript@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.1.0.tgz#2e9e54c4664ec3106c5b5630e249d3d6595c4911" + integrity sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= + +sade@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +schema-dts@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/schema-dts/-/schema-dts-1.1.5.tgz#9237725d305bac3469f02b292a035107595dc324" + integrity sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg== + +schema-utils@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + +schema-utils@^4.0.1, schema-utils@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0" + integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +schema-utils@^4.2.0: + version "4.3.3" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" + integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +section-matter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +selfsigned@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + +semver-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-4.0.0.tgz#3afcf5ed6d62259f5c72d0d5d50dffbdc9680df5" + integrity sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA== + dependencies: + semver "^7.3.5" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.2, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +semver@^7.5.4: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +serve-handler@^6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1" + integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + mime-types "2.1.18" + minimatch "3.1.2" + path-is-inside "1.0.2" + path-to-regexp "3.3.0" + range-parser "1.2.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" + integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +side-channel@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +sirv@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" + integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ== + dependencies: + "@polka/url" "^1.0.0-next.24" + mrmime "^2.0.0" + totalist "^3.0.0" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +sitemap@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" + integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== + dependencies: + "@types/node" "^17.0.5" + "@types/sax" "^1.2.1" + arg "^5.0.0" + sax "^1.2.4" + +skin-tone@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +sort-css-media-queries@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz#aa33cf4a08e0225059448b6c40eddbf9f1c8334c" + integrity sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA== + +sort-object-keys@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.3.tgz#bff833fe85cab147b34742e45863453c1e190b45" + integrity sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg== + +source-map-js@^1.0.1, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map-support@~0.5.20: + version "0.5.20" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" + integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.0: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + +space-separated-tokens@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b" + integrity sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +srcset@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/srcset/-/srcset-4.0.0.tgz#336816b665b14cd013ba545b6fe62357f86e65f4" + integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +std-env@^3.7.0: + version "3.8.1" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.1.tgz#2b81c631c62e3d0b964b87f099b8dcab6c9a5346" + integrity sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA== + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.2.tgz#13d113dc7449dc8ae4cb22c28883ee3fff8753e3" + integrity sha512-MTxTVcEkorNtBbNpoFJPEh0kKdM6+QbMjLbaxmvaPMmayOXdr/AIVIIJX7FReUVweRBFJfZepK4A4AKgwuFpMQ== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +style-to-js@^1.0.0: + version "1.1.16" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.16.tgz#e6bd6cd29e250bcf8fa5e6591d07ced7575dbe7a" + integrity sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw== + dependencies: + style-to-object "1.0.8" + +style-to-object@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292" + integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g== + dependencies: + inline-style-parser "0.2.4" + +style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" + +stylehacks@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6" + integrity sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg== + dependencies: + browserslist "^4.23.0" + postcss-selector-parser "^6.0.16" + +stylis@^4.1.3: + version "4.3.6" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.6.tgz#7c7b97191cb4f195f03ecab7d52f7902ed378320" + integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^3.0.2, svgo@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + +swr@^2.2.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.3.6.tgz#5fee0ee8a0762a16871ee371075cb09422b64f50" + integrity sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw== + dependencies: + dequal "^2.0.3" + use-sync-external-store "^1.4.0" + +synp@^1.9.10: + version "1.9.10" + resolved "https://registry.yarnpkg.com/synp/-/synp-1.9.10.tgz#53163321a600418c9b06af0db499939ffce12907" + integrity sha512-G9Z/TXTaBG1xNslUf3dHFidz/8tvvRaR560WWyOwyI7XrGGEGBTEIIg4hdRh1qFtz8mPYynAUYwWXUg/Zh0Pzw== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + bash-glob "^2.0.0" + colors "1.4.0" + commander "^7.2.0" + eol "^0.9.1" + lodash "4.17.21" + nmtree "^1.0.6" + semver "^7.3.5" + sort-object-keys "^1.1.3" + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + +tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.9: + version "5.3.14" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" + integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser@^5.15.1, terser@^5.31.1: + version "5.39.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a" + integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +terser@^5.7.2: + version "5.16.8" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.8.tgz#ccde583dabe71df3f4ed02b65eb6532e0fae15d5" + integrity sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +thingies@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/thingies/-/thingies-2.5.0.tgz#5f7b882c933b85989f8466b528a6247a6881e04f" + integrity sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw== + +throttleit@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-2.1.0.tgz#a7e4aa0bf4845a5bd10daa39ea0c783f631a07b4" + integrity sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tinypool@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-vfile@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/to-vfile/-/to-vfile-6.1.0.tgz#5f7a3f65813c2c4e34ee1f7643a5646344627699" + integrity sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw== + dependencies: + is-buffer "^2.0.0" + vfile "^4.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +tree-dump@^1.0.3, tree-dump@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4" + integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + +trough@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.0.2.tgz#94a3aa9d5ce379fc561f6244905b3f36b7458d96" + integrity sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w== + +ts-dedent@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" + integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== + +tslib@^2.0.0, tslib@^2.6.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tslib@^2.0.3: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tslib@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + +type-fest@^2.13.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +type-fest@^2.5.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.13.0.tgz#d1ecee38af29eb2e863b22299a3d68ef30d2abfb" + integrity sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +unescape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unescape/-/unescape-1.0.1.tgz#956e430f61cad8a4d57d82c518f5e6cc5d0dda96" + integrity sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ== + dependencies: + extend-shallow "^2.0.1" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71" + integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg== + +unicode-match-property-value-ecmascript@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz#65a7adfad8574c219890e219285ce4c64ed67eaa" + integrity sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + +unified@^10.0.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.1.tgz#345e349e3ab353ab612878338eb9d57b4dea1d46" + integrity sha512-v4ky1+6BN9X3pQrOdkFIPWAaeDsHPE1svRDxq7YpTc2plkIqFMwukfqM+l0ewpP9EfwARlt9pPFAeWYhHm8X9w== + dependencies: + "@types/unist" "^2.0.0" + bail "^2.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^5.0.0" + +unified@^11.0.0, unified@^11.0.3, unified@^11.0.4: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unified@^9.0.0, unified@^9.2.1: + version "9.2.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" + integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +unique-string@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-3.0.0.tgz#84a1c377aff5fd7a8bc6b55d8244b2bd90d75b9a" + integrity sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ== + dependencies: + crypto-random-string "^4.0.0" + +unist-builder@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04" + integrity sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-find-after@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-3.0.0.tgz#5c65fcebf64d4f8f496db46fa8fd0fbf354b43e6" + integrity sha512-ojlBqfsBftYXExNu3+hHLfJQ/X1jYY/9vdm4yZWjIbf0VuWF6CRufci1ZyoD/wV2TYMKxXUoNuoqwy+CkgzAiQ== + dependencies: + unist-util-is "^4.0.0" + +unist-util-generated@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113" + integrity sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw== + +unist-util-is@^4.0.0, unist-util-is@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" + integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== + +unist-util-is@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236" + integrity sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ== + +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position-from-estree@^1.0.0, unist-util-position-from-estree@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.1.tgz#96f4d543dfb0428edc01ebb928570b602d280c4c" + integrity sha512-xtoY50b5+7IH8tFbkw64gisG9tMSpxDjhX9TmaJJae/XuxQ9R/Kc8Nv1eOsf43Gt4KV/LkriMy9mptDr7XLcaw== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-position-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz#d94da4df596529d1faa3de506202f0c9a23f2200" + integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.1.tgz#f8484b2da19a897a0180556d160c28633070dbb9" + integrity sha512-mgy/zI9fQ2HlbOtTdr2w9lhVaiFUHWQnZrFF2EUoVOqtAUdzqMtNiD99qA5a1IcjWVR8O6aVYE9u7Z2z1v0SQA== + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-remove-position@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-4.0.1.tgz#d5b46a7304ac114c8d91990ece085ca7c2c135c8" + integrity sha512-0yDkppiIhDlPrfHELgB+NLQD5mfjup3a8UYclHruTJWmY74je8g+CIFr79x5f6AkmzSwlvKLbs63hC0meOMowQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-visit "^4.0.0" + +unist-util-remove@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-3.1.0.tgz#8042577e151dac989b7517976bfe4bac58f76ccd" + integrity sha512-rO/sIghl13eN8irs5OBN2a4RC10MsJdiePCfwrvnzGtgIbHcDXr2REr0qi9F2r/CIb1r9FyyFmcMRIGs+EyUFw== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.0.0" + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-stringify-position@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz#d517d2883d74d0daa0b565adc3d10a02b4a8cde9" + integrity sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit-parents@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2" + integrity sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + +unist-util-visit-parents@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.0.0.tgz#5ae2440f8710a0c18a2b4ba0c4471d18e1090494" + integrity sha512-CVaLOYPM/EaFTYMytbaju3Tw4QI3DHnHFnL358FkEu0hZOzSm/hqBdVwOQDR60jF5ZzhB1tlZlRH0ll/yekZIQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^2.0.0, unist-util-visit@^2.0.1, unist-util-visit@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +unist-util-visit@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-3.1.0.tgz#9420d285e1aee938c7d9acbafc8e160186dbaf7b" + integrity sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^4.0.0" + +unist-util-visit@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.0.tgz#f41e407a9e94da31594e6b1c9811c51ab0b3d8f5" + integrity sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.0.0" + +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +update-browserslist-db@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +update-browserslist-db@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" + integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +update-notifier@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-6.0.2.tgz#a6990253dfe6d5a02bd04fbb6a61543f55026b60" + integrity sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og== + dependencies: + boxen "^7.0.0" + chalk "^5.0.1" + configstore "^6.0.0" + has-yarn "^3.0.0" + import-lazy "^4.0.0" + is-ci "^3.0.1" + is-installed-globally "^0.4.0" + is-npm "^6.0.0" + is-yarn-global "^0.4.0" + latest-version "^7.0.0" + pupa "^3.1.0" + semver "^7.3.7" + semver-diff "^4.0.0" + xdg-basedir "^5.1.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-loader@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +use-sync-external-store@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util@^0.12.0: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" + integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + safe-buffer "^5.1.2" + which-typed-array "^1.1.2" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= + +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +uvu@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.3.tgz#3d83c5bc1230f153451877bfc7f4aea2392219ae" + integrity sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw== + dependencies: + dequal "^2.0.0" + diff "^5.0.0" + kleur "^4.0.3" + sade "^1.7.3" + +validate-peer-dependencies@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/validate-peer-dependencies/-/validate-peer-dependencies-2.1.0.tgz#1ad8218b1b168aeb500165f9de2a3f53269ece56" + integrity sha512-x+M+mp16g4N+jDQJO6a+AKnMHAViov9mRzYfgMYR6Bq+UTwewf8aTQsP+e1QH0oZrADqP7fuI/bEbl3CzRFhOQ== + dependencies: + resolve-package-path "^4.0.0" + semver "^7.3.2" + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +vfile-location@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" + integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== + +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile-message@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.0.tgz#5437035aa43185ff4b9210d32fada6c640e59143" + integrity sha512-4QJbBk+DkPEhBXq3f260xSaWtjE4gPKOfulzfMFF8ZNwaPZieWsg3iVlcmF04+eebzpcpeXOOFMfrYzJHVYg+g== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^3.0.0" + +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + +vfile@^5.0.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.0.tgz#4990c78cb3157005590ee8c930b71cd7fa6a006e" + integrity sha512-Tj44nY/48OQvarrE4FAjUfrv7GZOYzPbl5OD65HxVKwLJKMPU7zmfV8cCgCnzKWnSfYG2f3pxu+ALqs7j22xQQ== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + +vfile@^6.0.0, vfile@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +vlq@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" + integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== + +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +web-namespaces@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" + integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== + +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + +web-worker@^1.2.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.5.0.tgz#71b2b0fbcc4293e8f0aa4f6b8a3ffebff733dcc5" + integrity sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +webpack-bundle-analyzer@^4.10.2: + version "4.10.2" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz#633af2862c213730be3dbdf40456db171b60d5bd" + integrity sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw== + dependencies: + "@discoveryjs/json-ext" "0.5.7" + acorn "^8.0.4" + acorn-walk "^8.0.0" + commander "^7.2.0" + debounce "^1.2.1" + escape-string-regexp "^4.0.0" + gzip-size "^6.0.0" + html-escaper "^2.0.2" + opener "^1.5.2" + picocolors "^1.0.0" + sirv "^2.0.3" + ws "^7.3.1" + +webpack-dev-middleware@^7.4.2: + version "7.4.5" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz#d4e8720aa29cb03bc158084a94edb4594e3b7ac0" + integrity sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA== + dependencies: + colorette "^2.0.10" + memfs "^4.43.1" + mime-types "^3.0.1" + on-finished "^2.4.1" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz#96a143d50c58fef0c79107e61df911728d7ceb39" + integrity sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg== + dependencies: + "@types/bonjour" "^3.5.13" + "@types/connect-history-api-fallback" "^1.5.4" + "@types/express" "^4.17.21" + "@types/express-serve-static-core" "^4.17.21" + "@types/serve-index" "^1.9.4" + "@types/serve-static" "^1.15.5" + "@types/sockjs" "^0.3.36" + "@types/ws" "^8.5.10" + ansi-html-community "^0.0.8" + bonjour-service "^1.2.1" + chokidar "^3.6.0" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + express "^4.21.2" + graceful-fs "^4.2.6" + http-proxy-middleware "^2.0.9" + ipaddr.js "^2.1.0" + launch-editor "^2.6.1" + open "^10.0.3" + p-retry "^6.2.0" + schema-utils "^4.2.0" + selfsigned "^2.4.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^7.4.2" + ws "^8.18.0" + +webpack-merge@^5.9.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-merge@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-6.0.1.tgz#50c776868e080574725abc5869bd6e4ef0a16c6a" + integrity sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.1" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.88.1, webpack@^5.95.0: + version "5.98.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.98.0.tgz#44ae19a8f2ba97537978246072fb89d10d1fbd17" + integrity sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.6" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.14.0" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^4.3.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.11" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +webpackbar@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-6.0.1.tgz#5ef57d3bf7ced8b19025477bc7496ea9d502076b" + integrity sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q== + dependencies: + ansi-escapes "^4.3.2" + chalk "^4.1.2" + consola "^3.2.3" + figures "^3.2.0" + markdown-table "^2.0.0" + pretty-time "^1.1.0" + std-env "^3.7.0" + wrap-ansi "^7.0.0" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.2: + version "1.1.6" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.6.tgz#f3713d801da0720a7f26f50c596980a9f5c8b383" + integrity sha512-DdY984dGD5sQ7Tf+x1CkXzdg85b9uEel6nr4UkFg1LoE9OXv3uRuZhe5CoWdawhGACeFpEZXH8fFLQnDhbpm/Q== + dependencies: + available-typed-arrays "^1.0.4" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.6" + +which@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" + integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== + dependencies: + isexe "^3.1.1" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + +wildcard@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3" + integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.3.1: + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + +ws@^8.18.0: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + +wsl-utils@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/wsl-utils/-/wsl-utils-0.1.0.tgz#8783d4df671d4d50365be2ee4c71917a0557baab" + integrity sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw== + dependencies: + is-wsl "^3.1.0" + +xdg-basedir@^5.0.1, xdg-basedir@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-5.1.0.tgz#1efba19425e73be1bc6f2a6ceb52a3d2c884c0c9" + integrity sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ== + +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yarn-audit-fix@^9.3.10: + version "9.3.10" + resolved "https://registry.yarnpkg.com/yarn-audit-fix/-/yarn-audit-fix-9.3.10.tgz#42ece922adddf11477a696c106a172df611e681f" + integrity sha512-q4MeQuPTRNORLlxRwOJAdMOdMlqsgmbsym3SkNvD6kklMOVRWqZRlZyAlmmUepNgBaFOYI2NQCejgRz2VEIkAg== + dependencies: + "@types/find-cache-dir" "^3.2.1" + "@types/fs-extra" "^11.0.1" + "@types/lodash-es" "^4.17.6" + "@types/semver" "^7.3.13" + "@types/yarnpkg__lockfile" "^1.1.5" + "@yarnpkg/lockfile" "^1.1.0" + chalk "^5.2.0" + commander "^10.0.0" + find-cache-dir "^4.0.0" + find-up "^6.3.0" + fs-extra "^10.1.0" + globby "^13.1.3" + js-yaml "^4.1.0" + lodash-es "^4.17.21" + pkg-dir "^7.0.0" + semver "^7.3.8" + synp "^1.9.10" + tslib "^2.5.0" + +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + +zod@^4.1.8: + version "4.1.12" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.12.tgz#64f1ea53d00eab91853195653b5af9eee68970f0" + integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ== + +zwitch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== + +zwitch@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.2.tgz#91f8d0e901ffa3d66599756dde7f57b17c95dce1" + integrity sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==