From b77956089e7e03d11f37e77042f4b7533cf994a2 Mon Sep 17 00:00:00 2001 From: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com> Date: Tue, 3 Feb 2026 02:17:37 -0600 Subject: [PATCH 1/5] update deps --- bun.lock | 204 +++++++++++++++++++++++++++++++++++++-------------- package.json | 14 ++-- 2 files changed, 154 insertions(+), 64 deletions(-) diff --git a/bun.lock b/bun.lock index e8c837c8..c6983334 100644 --- a/bun.lock +++ b/bun.lock @@ -12,12 +12,12 @@ "@react-native-community/netinfo": "11.5.1", "@react-native-masked-view/masked-view": "^0.3.2", "@react-native-vector-icons/material-design-icons": "12.4.0", - "@react-navigation/bottom-tabs": "7.10.1", + "@react-navigation/bottom-tabs": "7.12.0", "@react-navigation/material-top-tabs": "7.4.13", "@react-navigation/native": "7.1.28", - "@react-navigation/native-stack": "7.11.0", + "@react-navigation/native-stack": "7.12.0", "@sentry/react-native": "7.8.0", - "@shopify/flash-list": "2.2.0", + "@shopify/flash-list": "2.2.1", "@tamagui/config": "1.144.3", "@tanstack/query-async-storage-persister": "5.90.12", "@tanstack/react-query": "5.90.12", @@ -63,8 +63,8 @@ "zustand": "5.0.10", }, "devDependencies": { - "@babel/core": "7.28.6", - "@babel/preset-env": "7.28.6", + "@babel/core": "7.29.0", + "@babel/preset-env": "7.29.0", "@babel/runtime": "7.28.6", "@eslint/eslintrc": "3.3.3", "@eslint/js": "9.39.2", @@ -76,7 +76,7 @@ "@react-native/typescript-config": "0.83.1", "@types/jest": "^30.0.0", "@types/lodash": "^4.17.21", - "@types/node": "25.0.9", + "@types/node": "25.2.0", "@types/react": "19.2.0", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "19.1.0", @@ -88,7 +88,7 @@ "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-native": "^5.0.0", - "globals": "17.0.0", + "globals": "17.3.0", "husky": "^9.1.7", "jest": "30.2.0", "jscodeshift": "^17.3.0", @@ -108,15 +108,15 @@ "react-native-nitro-modules", ], "packages": { - "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - "@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], "@babel/eslint-parser": ["@babel/eslint-parser@7.28.5", "", { "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.11.0", "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, "sha512-fcdRcWahONYo+JRnJg1/AekOacGvKx12Gu0qXJXFi2WBqQA1i7+O5PaxRB7kxE/Op94dExnCiiar6T09pvdHpA=="], - "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + "@babel/generator": ["@babel/generator@7.29.0", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ=="], "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], @@ -126,7 +126,7 @@ "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "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" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA=="], "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], @@ -156,7 +156,7 @@ "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], - "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], @@ -218,7 +218,7 @@ "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], - "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA=="], + "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w=="], "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g=="], @@ -240,7 +240,7 @@ "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="], - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA=="], + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw=="], "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="], @@ -268,11 +268,11 @@ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], - "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "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" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], + "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.0", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ=="], "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], - "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], + "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ=="], "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="], @@ -304,7 +304,7 @@ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], - "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw=="], + "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog=="], "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg=="], @@ -332,7 +332,7 @@ "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q=="], - "@babel/preset-env": ["@babel/preset-env@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@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.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@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.6", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@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.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@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.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.28.6", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@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.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@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" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw=="], + "@babel/preset-env": ["@babel/preset-env@7.29.0", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@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.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@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.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@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.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w=="], "@babel/preset-flow": ["@babel/preset-flow@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-flow-strip-types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg=="], @@ -346,11 +346,11 @@ "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], "@babel/traverse--for-generate-function-map": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], @@ -566,7 +566,7 @@ "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.83.1", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-MdmoAbQUTOdicCocm5XAFDJWsswxk7hxa6ALnm6Y88p01HFML0W593hAn6qOt9q6IM1KbAcebtH6oOd4gcQy8w=="], - "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.10.1", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-MirOzKEe/rRwPSE9HMrS4niIo0LyUhewlvd01TpzQ1ipuXjH2wJbzAM9gS/r62zriB6HMHz2OY6oIRduwQJtTw=="], + "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.12.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-/GtOfVWRligHG0mvX39I1FGdUWeWl0GVF2okEziQSQj0bOTrLIt7y44C3r/aCLkEpTVltCPGM3swqGTH3UfRCw=="], "@react-navigation/core": ["@react-navigation/core@7.14.0", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g=="], @@ -576,7 +576,7 @@ "@react-navigation/native": ["@react-navigation/native@7.1.28", "", { "dependencies": { "@react-navigation/core": "^7.14.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ=="], - "@react-navigation/native-stack": ["@react-navigation/native-stack@7.11.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-yNx9Wr4dfpOHpqjf2sGog4eH6KCYwTAEPlUPrKbvWlQbCRm5bglwPmaTXw9hTovX9v3HIa42yo7bXpbYfq4jzg=="], + "@react-navigation/native-stack": ["@react-navigation/native-stack@7.12.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-XmNJsPshjkNsahgbxNgGWQUq4s1l6HqH/Fei4QsjBNn/0mTvVrRVZwJ1XrY9YhWYvyiYkAN6/OmarWQaQJ0otQ=="], "@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="], @@ -620,7 +620,7 @@ "@sentry/types": ["@sentry/types@10.30.0", "", { "dependencies": { "@sentry/core": "10.30.0" } }, "sha512-tSyzG/JunWjbuQDDwP3DKgt8KP23ZSuNUEudMSv2jCF/956o8ksamPeidCTSVMXoEyTt5tvimWNeNvUFIFq3EA=="], - "@shopify/flash-list": ["@shopify/flash-list@2.2.0", "", { "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-mL61IofcfBNRZ/qazIf+pghGULkcZUQ7EZNldH1JBbIjtDb25ADSiQrt62ZTnRz0H5+bPFEZUmN9+WChHzX8pw=="], + "@shopify/flash-list": ["@shopify/flash-list@2.2.1", "", { "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-Squs4SneVNXl8WoCZ7zuer7SidbjXiSSafF5nAYQTtSzylchl5sc/P1swYY7snxWBZwYH9aHy7kCvuMPjiCh1g=="], "@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="], @@ -876,7 +876,7 @@ "@types/lodash": ["@types/lodash@4.17.23", "", {}, "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA=="], - "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], + "@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="], "@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="], @@ -1026,11 +1026,11 @@ "babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="], - "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="], + "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.15", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw=="], - "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], + "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ=="], - "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], + "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.6", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A=="], "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], @@ -1140,7 +1140,7 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + "core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="], "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], @@ -1378,7 +1378,7 @@ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - "globals": ["globals@17.0.0", "", {}, "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw=="], + "globals": ["globals@17.3.0", "", {}, "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], @@ -2272,18 +2272,26 @@ "@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "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" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + "@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], - "@babel/helper-define-polyfill-provider/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], "@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + "@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], + "@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], @@ -2294,6 +2302,8 @@ "@babel/helper-wrap-function/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@babel/helpers/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], @@ -2306,6 +2316,8 @@ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ=="], + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + "@babel/plugin-proposal-export-default-from/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], "@babel/plugin-syntax-async-generators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], @@ -2352,6 +2364,8 @@ "@babel/plugin-transform-block-scoped-functions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@babel/plugin-transform-classes/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + "@babel/plugin-transform-destructuring/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], "@babel/plugin-transform-destructuring/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], @@ -2380,20 +2394,14 @@ "@babel/plugin-transform-modules-amd/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - "@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@babel/plugin-transform-modules-systemjs/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], "@babel/plugin-transform-modules-umd/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - "@babel/plugin-transform-new-target/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + "@babel/plugin-transform-object-super/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], "@babel/plugin-transform-object-super/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], @@ -2420,6 +2428,12 @@ "@babel/plugin-transform-runtime/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], + "@babel/plugin-transform-shorthand-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], "@babel/plugin-transform-sticky-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], @@ -2446,6 +2460,12 @@ "@babel/preset-typescript/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + "@babel/template/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/template/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/template/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@babel/traverse--for-generate-function-map/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/traverse--for-generate-function-map/@babel/generator": ["@babel/generator@7.28.5", "", { "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" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], @@ -2564,6 +2584,8 @@ "@react-native/babel-preset/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + "@react-native/babel-preset/@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], + "@react-native/babel-preset/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="], "@react-native/babel-preset/@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw=="], @@ -2650,8 +2672,6 @@ "babel-plugin-jest-hoist/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "babel-plugin-polyfill-corejs2/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], - "babel-plugin-react-compiler/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "babel-preset-current-node-syntax/@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="], @@ -2672,6 +2692,8 @@ "connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "core-js-compat/browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], "eslint/@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], @@ -2978,7 +3000,13 @@ "write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], @@ -2988,6 +3016,20 @@ "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + "@babel/helper-module-imports/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "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" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], @@ -2998,6 +3040,14 @@ "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "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" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], @@ -3026,6 +3076,22 @@ "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@babel/plugin-transform-destructuring/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "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" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], @@ -3052,22 +3118,18 @@ "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - "@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "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" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/plugin-transform-react-jsx/@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], @@ -3076,6 +3138,16 @@ "@babel/plugin-transform-runtime/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "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" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "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" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "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" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.5", "", { "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" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], @@ -3212,6 +3284,8 @@ "@react-native/babel-preset/@babel/plugin-transform-modules-commonjs/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@react-native/babel-preset/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@react-native/babel-preset/@babel/plugin-transform-nullish-coalescing-operator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], "@react-native/babel-preset/@babel/plugin-transform-numeric-separator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], @@ -3336,6 +3410,14 @@ "connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "core-js-compat/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], + + "core-js-compat/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001767", "", {}, "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ=="], + + "core-js-compat/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.283", "", {}, "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w=="], + + "core-js-compat/browserslist/update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "eslint-plugin-react-hooks/@babel/core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "eslint-plugin-react-hooks/@babel/core/@babel/generator": ["@babel/generator@7.28.5", "", { "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" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], @@ -3648,8 +3730,6 @@ "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], @@ -3688,6 +3768,12 @@ "@babel/plugin-transform-runtime/@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "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" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "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" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "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" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "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" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], @@ -3956,6 +4042,10 @@ "slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], + "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + + "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + "@babel/preset-typescript/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/preset-typescript/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], diff --git a/package.json b/package.json index f9273918..dfdb2ce2 100644 --- a/package.json +++ b/package.json @@ -44,12 +44,12 @@ "@react-native-community/netinfo": "11.5.1", "@react-native-masked-view/masked-view": "^0.3.2", "@react-native-vector-icons/material-design-icons": "12.4.0", - "@react-navigation/bottom-tabs": "7.10.1", + "@react-navigation/bottom-tabs": "7.12.0", "@react-navigation/material-top-tabs": "7.4.13", "@react-navigation/native": "7.1.28", - "@react-navigation/native-stack": "7.11.0", + "@react-navigation/native-stack": "7.12.0", "@sentry/react-native": "7.8.0", - "@shopify/flash-list": "2.2.0", + "@shopify/flash-list": "2.2.1", "@tamagui/config": "1.144.3", "@tanstack/query-async-storage-persister": "5.90.12", "@tanstack/react-query": "5.90.12", @@ -95,8 +95,8 @@ "zustand": "5.0.10" }, "devDependencies": { - "@babel/core": "7.28.6", - "@babel/preset-env": "7.28.6", + "@babel/core": "7.29.0", + "@babel/preset-env": "7.29.0", "@babel/runtime": "7.28.6", "@eslint/eslintrc": "3.3.3", "@eslint/js": "9.39.2", @@ -108,7 +108,7 @@ "@react-native/typescript-config": "0.83.1", "@types/jest": "^30.0.0", "@types/lodash": "^4.17.21", - "@types/node": "25.0.9", + "@types/node": "25.2.0", "@types/react": "19.2.0", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "19.1.0", @@ -120,7 +120,7 @@ "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-native": "^5.0.0", - "globals": "17.0.0", + "globals": "17.3.0", "husky": "^9.1.7", "jest": "30.2.0", "jscodeshift": "^17.3.0", From 927f6aa97378412a8e068da2bef76d7e02076aed Mon Sep 17 00:00:00 2001 From: Stephen Arg Date: Tue, 3 Feb 2026 23:35:08 +0100 Subject: [PATCH 2/5] Added sorting option to library for artists, albums, tracks (#977) Co-authored-by: StephenArg --- src/api/queries/album/index.ts | 49 ++++++- src/api/queries/artist/index.ts | 36 ++++- src/api/queries/track/index.ts | 29 +++- src/components/Albums/component.tsx | 16 +- src/components/Artists/component.tsx | 5 +- .../Global/components/Track/index.tsx | 11 +- .../components/alphabetical-selector.tsx | 8 +- .../Library/components/albums-tab.tsx | 25 +++- .../Library/components/artists-tab.tsx | 24 ++- .../Library/components/tracks-tab.tsx | 26 +++- src/components/Library/tab-bar.tsx | 71 ++++++--- src/components/SortOptions/index.tsx | 137 ++++++++++++++++++ src/components/Tracks/component.tsx | 23 ++- src/screens/SortOptions/index.tsx | 6 + src/screens/index.tsx | 12 ++ src/screens/types.d.ts | 7 +- src/stores/library.ts | 64 +++++++- src/utils/query-selectors.ts | 41 +++++- 18 files changed, 524 insertions(+), 66 deletions(-) create mode 100644 src/components/SortOptions/index.tsx create mode 100644 src/screens/SortOptions/index.tsx diff --git a/src/api/queries/album/index.ts b/src/api/queries/album/index.ts index 5cca75c6..0c74a34d 100644 --- a/src/api/queries/album/index.ts +++ b/src/api/queries/album/index.ts @@ -28,16 +28,51 @@ const useAlbums: () => [ const user = getUser() const [library] = useJellifyLibrary() - const isFavorites = useLibraryStore((state) => state.filters.albums.isFavorites) + const { + filters, + sortBy: librarySortByState, + sortDescending: librarySortDescendingState, + } = useLibraryStore() + const rawAlbumSortBy = librarySortByState.albums ?? ItemSortBy.SortName + const albumSortByOptions = [ + ItemSortBy.Name, + ItemSortBy.SortName, + ItemSortBy.Album, + ItemSortBy.Artist, + ItemSortBy.PlayCount, + ItemSortBy.DateCreated, + ItemSortBy.PremiereDate, + ] as ItemSortBy[] + const librarySortBy = albumSortByOptions.includes(rawAlbumSortBy as ItemSortBy) + ? (rawAlbumSortBy as ItemSortBy) + : ItemSortBy.Album + const sortDescending = librarySortDescendingState.albums ?? false + const isFavorites = filters.albums.isFavorites const albumPageParams = useRef>(new Set()) - // Memize the expensive albums select function - const selectAlbums = (data: InfiniteData) => - flattenInfiniteQueryPages(data, albumPageParams) + // Add letter sections when sorting by name/album/artist (for A-Z selector) + const isSortByLetter = + librarySortBy === ItemSortBy.Name || + librarySortBy === ItemSortBy.SortName || + librarySortBy === ItemSortBy.Album || + librarySortBy === ItemSortBy.Artist + + const selectAlbums = (data: InfiniteData) => { + if (!isSortByLetter) return data.pages.flatMap((page) => page) + return flattenInfiniteQueryPages(data, albumPageParams, { + sortBy: librarySortBy === ItemSortBy.Artist ? ItemSortBy.Artist : undefined, + }) + } const albumsInfiniteQuery = useInfiniteQuery({ - queryKey: [QueryKeys.InfiniteAlbums, isFavorites, library?.musicLibraryId], + queryKey: [ + QueryKeys.InfiniteAlbums, + isFavorites, + library?.musicLibraryId, + librarySortBy, + sortDescending, + ], queryFn: ({ pageParam }) => fetchAlbums( api, @@ -45,8 +80,8 @@ const useAlbums: () => [ library, pageParam, isFavorites, - [ItemSortBy.SortName], - [SortOrder.Ascending], + [librarySortBy ?? ItemSortBy.SortName], + [sortDescending ? SortOrder.Descending : SortOrder.Ascending], ), initialPageParam: 0, select: selectAlbums, diff --git a/src/api/queries/artist/index.ts b/src/api/queries/artist/index.ts index 05b3c6fb..53596a63 100644 --- a/src/api/queries/artist/index.ts +++ b/src/api/queries/artist/index.ts @@ -44,17 +44,41 @@ export const useAlbumArtists: () => [ const [user] = useJellifyUser() const [library] = useJellifyLibrary() - const { filters, sortDescending } = useLibraryStore() + const { + filters, + sortBy: librarySortByState, + sortDescending: librarySortDescendingState, + } = useLibraryStore() + const rawArtistSortBy = librarySortByState.artists ?? ItemSortBy.SortName + // Artists tab only supports sort by name + const librarySortBy = + rawArtistSortBy === ItemSortBy.SortName || rawArtistSortBy === ItemSortBy.Name + ? rawArtistSortBy + : ItemSortBy.SortName + const sortDescending = librarySortDescendingState.artists ?? false const isFavorites = filters.artists.isFavorites const artistPageParams = useRef>(new Set()) - // Memoize the expensive artists select function - const selectArtists = (data: InfiniteData) => - flattenInfiniteQueryPages(data, artistPageParams) + const isSortByName = + librarySortBy === ItemSortBy.Name || + librarySortBy === ItemSortBy.SortName || + librarySortBy === ItemSortBy.Artist + + // Only add letter sections when sorting by name (for A-Z selector) + const selectArtists = (data: InfiniteData) => { + if (!isSortByName) return data.pages.flatMap((page) => page) + return flattenInfiniteQueryPages(data, artistPageParams) + } const artistsInfiniteQuery = useInfiniteQuery({ - queryKey: [QueryKeys.InfiniteArtists, isFavorites, sortDescending, library?.musicLibraryId], + queryKey: [ + QueryKeys.InfiniteArtists, + isFavorites, + sortDescending, + library?.musicLibraryId, + librarySortBy, + ], queryFn: ({ pageParam }: { pageParam: number }) => fetchArtists( api, @@ -62,7 +86,7 @@ export const useAlbumArtists: () => [ library, pageParam, isFavorites, - [ItemSortBy.SortName], + [librarySortBy ?? ItemSortBy.SortName], [sortDescending ? SortOrder.Descending : SortOrder.Ascending], ), select: selectArtists, diff --git a/src/api/queries/track/index.ts b/src/api/queries/track/index.ts index 2666fe8c..c9c0f586 100644 --- a/src/api/queries/track/index.ts +++ b/src/api/queries/track/index.ts @@ -33,7 +33,13 @@ const useTracks: ( const api = getApi() const user = getUser() const [library] = useJellifyLibrary() - const { filters, sortDescending: isLibrarySortDescending } = useLibraryStore() + const { + filters, + sortBy: librarySortByState, + sortDescending: librarySortDescendingState, + } = useLibraryStore() + const librarySortBy = librarySortByState.tracks ?? undefined + const isLibrarySortDescending = librarySortDescendingState.tracks ?? false const isLibraryFavorites = filters.tracks.isFavorites const isDownloaded = filters.tracks.isDownloaded ?? false const isLibraryUnplayed = filters.tracks.isUnplayed ?? false @@ -50,7 +56,7 @@ const useTracks: ( : isLibraryFavorites const isUnplayed = isUnplayedParam !== undefined ? isUnplayedParam : artistId ? undefined : isLibraryUnplayed - const finalSortBy = sortBy ?? ItemSortBy.Name + const finalSortBy = librarySortBy ?? sortBy ?? ItemSortBy.Name const finalSortOrder = sortOrder ?? (isLibrarySortDescending ? SortOrder.Descending : SortOrder.Ascending) @@ -59,11 +65,22 @@ const useTracks: ( const trackPageParams = useRef>(new Set()) const selectTracks = (data: InfiniteData) => { - if (finalSortBy === ItemSortBy.SortName || finalSortBy === ItemSortBy.Name) { - return flattenInfiniteQueryPages(data, trackPageParams) - } else { - return data.pages.flatMap((page) => page) + if ( + finalSortBy === ItemSortBy.SortName || + finalSortBy === ItemSortBy.Name || + finalSortBy === ItemSortBy.Album || + finalSortBy === ItemSortBy.Artist + ) { + return flattenInfiniteQueryPages(data, trackPageParams, { + sortBy: + finalSortBy === ItemSortBy.Artist + ? ItemSortBy.Artist + : finalSortBy === ItemSortBy.Album + ? ItemSortBy.Album + : undefined, + }) } + return data.pages.flatMap((page) => page) } const tracksInfiniteQuery = useInfiniteQuery({ diff --git a/src/components/Albums/component.tsx b/src/components/Albums/component.tsx index 7a92e9a8..d32aab73 100644 --- a/src/components/Albums/component.tsx +++ b/src/components/Albums/component.tsx @@ -4,6 +4,7 @@ import { Text } from '../Global/helpers/text' import { FlashList, FlashListRef } from '@shopify/flash-list' import { UseInfiniteQueryResult } from '@tanstack/react-query' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' import ItemRow from '../Global/components/item-row' import { useNavigation } from '@react-navigation/native' import LibraryStackParamList from '../../screens/Library/types' @@ -19,6 +20,8 @@ import MAX_ITEMS_IN_RECYCLE_POOL from '../../configs/library.config' interface AlbumsProps { albumsInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error> showAlphabeticalSelector: boolean + sortBy?: ItemSortBy + sortDescending?: boolean albumPageParams?: RefObject> } @@ -26,6 +29,8 @@ export default function Albums({ albumsInfiniteQuery, albumPageParams, showAlphabeticalSelector, + sortBy, + sortDescending, }: AlbumsProps): React.JSX.Element { const theme = useTheme() @@ -40,11 +45,11 @@ export default function Albums({ const pendingLetterRef = useRef(null) const stickyHeaderIndices = - !showAlphabeticalSelector || !albumsInfiniteQuery.data + !showAlphabeticalSelector || !albumsInfiniteQuery.data || sortBy === ItemSortBy.Artist ? [] : albumsInfiniteQuery.data - .map((album, index) => (typeof album === 'string' ? index : 0)) - .filter((value, index, indices) => indices.indexOf(value) === index) + .map((album, index) => (typeof album === 'string' ? index : null)) + .filter((v): v is number => v !== null) const { mutateAsync: alphabetSelectorMutate, isPending: isAlphabetSelectorPending } = useAlphabetSelector((letter) => (pendingLetterRef.current = letter.toUpperCase())) @@ -68,7 +73,9 @@ export default function Albums({ item: BaseItemDto | string | number }) => typeof album === 'string' ? ( - + sortBy === ItemSortBy.Artist ? null : ( + + ) ) : typeof album === 'number' ? null : typeof album === 'object' ? ( ) : null @@ -143,6 +150,7 @@ export default function Albums({ {showAlphabeticalSelector && albumPageParams && ( alphabetSelectorMutate({ letter, diff --git a/src/components/Artists/component.tsx b/src/components/Artists/component.tsx index 53102064..d44d445e 100644 --- a/src/components/Artists/component.tsx +++ b/src/components/Artists/component.tsx @@ -1,4 +1,4 @@ -import React, { RefObject, useEffect, useRef } from 'react' +import React, { RefObject, useEffect, useRef, useState } from 'react' import { Separator, useTheme, XStack, YStack } from 'tamagui' import { Text } from '../Global/helpers/text' import ItemRow from '../Global/components/item-row' @@ -22,6 +22,7 @@ export interface ArtistsProps { Error > showAlphabeticalSelector: boolean + sortDescending?: boolean artistPageParams?: RefObject> } @@ -35,6 +36,7 @@ export interface ArtistsProps { export default function Artists({ artistsInfiniteQuery, showAlphabeticalSelector, + sortDescending, artistPageParams, }: ArtistsProps): React.JSX.Element { const theme = useTheme() @@ -158,6 +160,7 @@ export default function Artists({ {showAlphabeticalSelector && artistPageParams && ( alphabetSelectorMutate({ letter, diff --git a/src/components/Global/components/Track/index.tsx b/src/components/Global/components/Track/index.tsx index fb6ec606..089e6484 100644 --- a/src/components/Global/components/Track/index.tsx +++ b/src/components/Global/components/Track/index.tsx @@ -35,6 +35,8 @@ export interface TrackProps { invertedColors?: boolean | undefined testID?: string | undefined editing?: boolean | undefined + sortingByAlbum?: boolean | undefined + sortingByReleasedDate?: boolean | undefined } export default function Track({ @@ -50,6 +52,8 @@ export default function Track({ isNested, invertedColors, editing, + sortingByAlbum, + sortingByReleasedDate, }: TrackProps): React.JSX.Element { const theme = useTheme() const [artworkAreaWidth, setArtworkAreaWidth] = useState(0) @@ -132,7 +136,12 @@ export default function Track({ : undefined // Memoize artists text - const artistsText = track.Artists?.join(' • ') ?? '' + const artistsText = + (sortingByAlbum + ? track.Album + : sortingByReleasedDate + ? `${track.ProductionYear?.toString()} • ${track.Artists?.join(' • ')}` + : track.Artists?.join(' • ')) ?? '' // Memoize track name const trackName = track.Name ?? 'Untitled Track' diff --git a/src/components/Global/components/alphabetical-selector.tsx b/src/components/Global/components/alphabetical-selector.tsx index f1e5aa5b..930a0517 100644 --- a/src/components/Global/components/alphabetical-selector.tsx +++ b/src/components/Global/components/alphabetical-selector.tsx @@ -9,7 +9,8 @@ import { UseInfiniteQueryResult, useMutation } from '@tanstack/react-query' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client' import { triggerHaptic } from '../../../hooks/use-haptic-feedback' -const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') +const alphabetAtoZ = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') +const alphabetZtoA = '#ZYXWVUTSRQPONMLKJIHGFEDCBA'.split('') /** * A component that displays a list of hardcoded alphabet letters and a selected letter overlay * When a letter is selected, the overlay will be shown and the callback function will be called @@ -18,16 +19,19 @@ const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') * The overlay will be hidden after 200ms * * @param onLetterSelect - Callback function to be called when a letter is selected + * @param reverseOrder - When true, display #, Z-A (for descending sort) instead of #, A-Z * @returns A component that displays a list of letters and a selected letter overlay */ export default function AZScroller({ onLetterSelect, alphabet: customAlphabet, + reverseOrder, }: { onLetterSelect: (letter: string) => Promise alphabet?: string[] + reverseOrder?: boolean }) { - const alphabetToUse = customAlphabet ?? alphabet + const alphabetToUse = customAlphabet ?? (reverseOrder ? alphabetZtoA : alphabetAtoZ) const theme = useTheme() const [operationPending, setOperationPending] = useState(false) diff --git a/src/components/Library/components/albums-tab.tsx b/src/components/Library/components/albums-tab.tsx index d3699652..b707b396 100644 --- a/src/components/Library/components/albums-tab.tsx +++ b/src/components/Library/components/albums-tab.tsx @@ -1,13 +1,36 @@ import useAlbums from '../../../api/queries/album' import Albums from '../../Albums/component' +import useLibraryStore from '../../../stores/library' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' function AlbumsTab(): React.JSX.Element { const [albumPageParams, albumsInfiniteQuery] = useAlbums() + const sortBy = useLibraryStore((state) => { + const sb = state.sortBy as Record | string + if (typeof sb === 'string') return sb + return sb?.albums ?? ItemSortBy.Album + }) + const sortDescending = useLibraryStore((state) => { + const sd = state.sortDescending as Record | boolean + if (typeof sd === 'boolean') return sd + return sd?.albums ?? false + }) + const hasLetterSections = + albumsInfiniteQuery.data?.some((item) => typeof item === 'string') ?? false + const showAlphabeticalSelector = + hasLetterSections || + sortBy === ItemSortBy.Name || + sortBy === ItemSortBy.SortName || + sortBy === ItemSortBy.Album || + sortBy === ItemSortBy.Artist + return ( ) diff --git a/src/components/Library/components/artists-tab.tsx b/src/components/Library/components/artists-tab.tsx index bec49ff8..a796ab00 100644 --- a/src/components/Library/components/artists-tab.tsx +++ b/src/components/Library/components/artists-tab.tsx @@ -1,13 +1,35 @@ import { useAlbumArtists } from '../../../api/queries/artist' import Artists from '../../Artists/component' +import useLibraryStore from '../../../stores/library' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' function ArtistsTab(): React.JSX.Element { const [artistPageParams, artistsInfiniteQuery] = useAlbumArtists() + const sortBy = useLibraryStore((state) => { + const sb = state.sortBy as Record | string + if (typeof sb === 'string') return sb + return sb?.artists ?? ItemSortBy.SortName + }) + const sortDescending = useLibraryStore((state) => { + const sd = state.sortDescending as Record | boolean + if (typeof sd === 'boolean') return sd + return sd?.artists ?? false + }) + const hasLetterSections = + artistsInfiniteQuery.data?.some((item) => typeof item === 'string') ?? false + // Artists tab only sorts by name, so always show A-Z when we have letter sections + const showAlphabeticalSelector = + hasLetterSections || + sortBy === ItemSortBy.Name || + sortBy === ItemSortBy.SortName || + sortBy === ItemSortBy.Artist + return ( ) diff --git a/src/components/Library/components/tracks-tab.tsx b/src/components/Library/components/tracks-tab.tsx index ce47ec69..6b238745 100644 --- a/src/components/Library/components/tracks-tab.tsx +++ b/src/components/Library/components/tracks-tab.tsx @@ -6,12 +6,32 @@ import LibraryStackParamList from '@/src/screens/Library/types' import { NativeStackNavigationProp } from '@react-navigation/native-stack' import useTracks from '../../../api/queries/track' import useLibraryStore from '../../../stores/library' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' function TracksTab(): React.JSX.Element { const [trackPageParams, tracksInfiniteQuery] = useTracks() - const { filters } = useLibraryStore() + const filters = useLibraryStore((state) => state.filters) + const sortBy = useLibraryStore((state) => { + const sb = state.sortBy as Record | string + if (typeof sb === 'string') return sb + return sb?.tracks ?? ItemSortBy.Name + }) + const sortDescending = useLibraryStore((state) => { + const sd = state.sortDescending as Record | boolean + if (typeof sd === 'boolean') return sd + return sd?.tracks ?? false + }) const { isFavorites, isDownloaded } = filters.tracks + // Show A-Z when sort is by name OR when data already has letter sections (e.g. after sort change) + const hasLetterSections = + tracksInfiniteQuery.data?.some((item) => typeof item === 'string') ?? false + const showAlphabeticalSelector = + hasLetterSections || + sortBy === ItemSortBy.Name || + sortBy === ItemSortBy.SortName || + sortBy === ItemSortBy.Album || + sortBy === ItemSortBy.Artist const navigation = useNavigation>() @@ -20,7 +40,9 @@ function TracksTab(): React.JSX.Element { navigation={navigation} tracksInfiniteQuery={tracksInfiniteQuery} queue={isFavorites ? 'Favorite Tracks' : isDownloaded ? 'Downloaded Tracks' : 'Library'} - showAlphabeticalSelector={true} + showAlphabeticalSelector={showAlphabeticalSelector} + sortBy={sortBy as ItemSortBy} + sortDescending={sortDescending} trackPageParams={trackPageParams} /> ) diff --git a/src/components/Library/tab-bar.tsx b/src/components/Library/tab-bar.tsx index b032ab33..1f4e02cc 100644 --- a/src/components/Library/tab-bar.tsx +++ b/src/components/Library/tab-bar.tsx @@ -99,29 +99,56 @@ function LibraryTabBar(props: MaterialTopTabBarProps) { )} {props.state.routes[props.state.index].name !== 'Playlists' && ( - { - triggerHaptic('impactLight') - if (navigationRef.isReady()) { - navigationRef.navigate('Filters', { - currentTab: currentTab as 'Tracks' | 'Albums' | 'Artists', - }) - } - }} - pressStyle={{ opacity: 0.6 }} - animation='quick' - alignItems={'center'} - justifyContent={'center'} - > - + <> + { + triggerHaptic('impactLight') + if (navigationRef.isReady()) { + navigationRef.navigate('SortOptions', { + currentTab: currentTab as + | 'Tracks' + | 'Albums' + | 'Artists', + }) + } + }} + pressStyle={{ opacity: 0.6 }} + animation='quick' + alignItems={'center'} + justifyContent={'center'} + > + - - Filter - - + Sort + + + { + triggerHaptic('impactLight') + if (navigationRef.isReady()) { + navigationRef.navigate('Filters', { + currentTab: currentTab as + | 'Tracks' + | 'Albums' + | 'Artists', + }) + } + }} + pressStyle={{ opacity: 0.6 }} + animation='quick' + alignItems={'center'} + justifyContent={'center'} + > + + + + Filter + + + )} {props.state.routes[props.state.index].name !== 'Playlists' && diff --git a/src/components/SortOptions/index.tsx b/src/components/SortOptions/index.tsx new file mode 100644 index 00000000..9361bb1a --- /dev/null +++ b/src/components/SortOptions/index.tsx @@ -0,0 +1,137 @@ +import React, { useEffect } from 'react' +import { YStack } from 'tamagui' +import { Text } from '../Global/helpers/text' +import { RadioGroup } from 'tamagui' +import { RadioGroupItemWithLabel } from '../Global/helpers/radio-group-item-with-label' +import useLibraryStore, { LibraryTab } from '../../stores/library' +import { triggerHaptic } from '../../hooks/use-haptic-feedback' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' + +const TRACK_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [ + { value: ItemSortBy.Name, label: 'Track' }, + { value: ItemSortBy.Album, label: 'Album' }, + { value: ItemSortBy.Artist, label: 'Artist' }, + { value: ItemSortBy.DateCreated, label: 'Date Added' }, + { value: ItemSortBy.PlayCount, label: 'Play Count' }, + { value: ItemSortBy.PremiereDate, label: 'Release Date' }, + { value: ItemSortBy.Runtime, label: 'Runtime' }, +] + +const ALBUM_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [ + { value: ItemSortBy.SortName, label: 'Album' }, + { value: ItemSortBy.Artist, label: 'Artist' }, + { value: ItemSortBy.PlayCount, label: 'Play Count' }, + { value: ItemSortBy.DateCreated, label: 'Date Added' }, + { value: ItemSortBy.PremiereDate, label: 'Release Date' }, +] + +const ARTIST_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [ + { value: ItemSortBy.SortName, label: 'Artist' }, +] + +function toLibraryTab(tab: string | undefined): LibraryTab { + const lower = tab?.toLowerCase() + return lower === 'albums' || lower === 'artists' ? lower : 'tracks' +} + +function getSortByOptionsForTab(tab: LibraryTab): { value: ItemSortBy; label: string }[] { + switch (tab) { + case 'albums': + return ALBUM_SORT_OPTIONS + case 'artists': + return ARTIST_SORT_OPTIONS + default: + return TRACK_SORT_OPTIONS + } +} + +const DATE_SORT_BY: ItemSortBy[] = [ItemSortBy.DateCreated, ItemSortBy.PremiereDate] +const NUMERIC_SORT_BY: ItemSortBy[] = [ItemSortBy.PlayCount, ItemSortBy.Runtime] + +function getSortOrderLabels(sortBy: ItemSortBy): { ascending: string; descending: string } { + if (DATE_SORT_BY.includes(sortBy)) { + return { ascending: 'Oldest', descending: 'Newest' } + } + if (NUMERIC_SORT_BY.includes(sortBy)) { + return { ascending: 'Lowest', descending: 'Highest' } + } + return { ascending: 'Ascending', descending: 'Descending' } +} + +export default function SortOptions({ + currentTab, +}: { + currentTab?: 'Tracks' | 'Albums' | 'Artists' +}): React.JSX.Element { + const tab = toLibraryTab(currentTab) + const { getSortBy, getSortDescending, setSortBy, setSortDescending } = useLibraryStore() + const sortByOptions = getSortByOptionsForTab(tab) + const currentSortBy = getSortBy(tab) + const effectiveSortBy = sortByOptions.some((o) => o.value === currentSortBy) + ? currentSortBy + : sortByOptions[0]!.value + const sortDescending = getSortDescending(tab) + const sortOrderLabels = getSortOrderLabels(effectiveSortBy) + + const handleSortByChange = (value: string) => { + triggerHaptic('impactLight') + setSortBy(tab, value as ItemSortBy) + } + + const handleSortOrderChange = (value: string) => { + triggerHaptic('impactLight') + setSortDescending(tab, value === 'descending') + } + + // When opening the sheet, if stored sort is not in allowed options (e.g. after tab-specific change), persist the fallback + useEffect(() => { + if (effectiveSortBy !== currentSortBy) { + setSortBy(tab, effectiveSortBy) + } + }, [tab, effectiveSortBy, currentSortBy, setSortBy]) + + return ( + + + + Sort By + + + + {sortByOptions.map((option) => ( + + ))} + + + + + + + Sort Order + + + + + + + + + + ) +} diff --git a/src/components/Tracks/component.tsx b/src/components/Tracks/component.tsx index 43e05dad..546e1a82 100644 --- a/src/components/Tracks/component.tsx +++ b/src/components/Tracks/component.tsx @@ -2,6 +2,7 @@ import React, { RefObject, useRef, useEffect } from 'react' import Track from '../Global/components/Track' import { useTheme, XStack, YStack } from 'tamagui' import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' import { Queue } from '../../player/types/queue-item' import { FlashList, FlashListRef } from '@shopify/flash-list' import { NativeStackNavigationProp } from '@react-navigation/native-stack' @@ -20,6 +21,8 @@ interface TracksProps { tracksInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error> trackPageParams?: RefObject> showAlphabeticalSelector?: boolean + sortBy?: ItemSortBy + sortDescending?: boolean navigation: Pick, 'navigate' | 'dispatch'> queue: Queue } @@ -28,6 +31,8 @@ export default function Tracks({ tracksInfiniteQuery, trackPageParams, showAlphabeticalSelector, + sortBy, + sortDescending, navigation, queue, }: TracksProps): React.JSX.Element { @@ -38,11 +43,16 @@ export default function Tracks({ const pendingLetterRef = useRef(null) const stickyHeaderIndicies = (() => { - if (!showAlphabeticalSelector || !tracksInfiniteQuery.data) return [] - + if ( + !showAlphabeticalSelector || + !tracksInfiniteQuery.data || + sortBy === ItemSortBy.Artist || + sortBy === ItemSortBy.Album + ) + return [] return tracksInfiniteQuery.data - .map((track, index) => (typeof track === 'string' ? index : 0)) - .filter((value, index, indices) => indices.indexOf(value) === index) + .map((track, index) => (typeof track === 'string' ? index : null)) + .filter((v): v is number => v !== null) })() const { mutateAsync: alphabetSelectorMutate, isPending: isAlphabetSelectorPending } = @@ -72,6 +82,7 @@ export default function Tracks({ }) => { switch (typeof track) { case 'string': + if (sortBy === ItemSortBy.Artist || sortBy === ItemSortBy.Album) return null return case 'object': return track.Type === BaseItemKind.Audio ? ( @@ -83,6 +94,8 @@ export default function Tracks({ testID={`track-item-${index}`} tracklist={tracks.slice(tracks.indexOf(track), tracks.indexOf(track) + 50)} queue={queue} + sortingByAlbum={sortBy === ItemSortBy.Album} + sortingByReleasedDate={sortBy === ItemSortBy.PremiereDate} /> ) : ( @@ -138,6 +151,7 @@ export default function Tracks({ return ( alphabetSelectorMutate({ letter, diff --git a/src/screens/SortOptions/index.tsx b/src/screens/SortOptions/index.tsx new file mode 100644 index 00000000..53072c52 --- /dev/null +++ b/src/screens/SortOptions/index.tsx @@ -0,0 +1,6 @@ +import SortOptionsComponent from '../../components/SortOptions/index' +import { SortOptionsProps } from '../types' + +export default function SortOptionsSheet({ route }: SortOptionsProps): React.JSX.Element { + return +} diff --git a/src/screens/index.tsx b/src/screens/index.tsx index 41e6da33..38947c43 100644 --- a/src/screens/index.tsx +++ b/src/screens/index.tsx @@ -17,6 +17,7 @@ import DeletePlaylist from './Library/delete-playlist' import { Platform } from 'react-native' import { formatArtistNames } from '../utils/formatting/artist-names' import FiltersSheet from './Filters' +import SortOptionsSheet from './SortOptions' import GenreSelectionScreen from './GenreSelection' const RootStack = createNativeStackNavigator() @@ -88,6 +89,17 @@ export default function Root(): React.JSX.Element { }} /> + + export type FiltersProps = NativeStackScreenProps +export type SortOptionsProps = NativeStackScreenProps export type GenreSelectionProps = NativeStackScreenProps export type GenresProps = { diff --git a/src/stores/library.ts b/src/stores/library.ts index 520a1200..08465e33 100644 --- a/src/stores/library.ts +++ b/src/stores/library.ts @@ -1,6 +1,9 @@ import { createJSONStorage, devtools, persist } from 'zustand/middleware' import { mmkvStateStorage } from '../constants/storage' import { create } from 'zustand' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' + +export type LibraryTab = 'tracks' | 'albums' | 'artists' type TabFilterState = { isFavorites: boolean | undefined @@ -9,9 +12,16 @@ type TabFilterState = { genreIds?: string[] // Only for Tracks tab } +type SortState = Record +type SortOrderState = Record + type LibraryStore = { - sortDescending: boolean - setSortDescending: (sortDescending: boolean) => void + sortBy: SortState + sortDescending: SortOrderState + setSortBy: (tab: LibraryTab, sortBy: ItemSortBy) => void + setSortDescending: (tab: LibraryTab, sortDescending: boolean) => void + getSortBy: (tab: LibraryTab) => ItemSortBy + getSortDescending: (tab: LibraryTab) => boolean filters: { tracks: TabFilterState albums: TabFilterState @@ -28,8 +38,54 @@ const useLibraryStore = create()( devtools( persist( (set, get) => ({ - sortDescending: false, - setSortDescending: (sortDescending: boolean) => set({ sortDescending }), + sortBy: { + tracks: ItemSortBy.Name, + albums: ItemSortBy.Name, + artists: ItemSortBy.SortName, + }, + sortDescending: { + tracks: false, + albums: false, + artists: false, + }, + setSortBy: (tab: LibraryTab, sortBy: ItemSortBy) => + set((state) => { + const current = state.sortBy as SortState | string + const next: SortState = + typeof current === 'object' && current !== null && 'tracks' in current + ? { ...current, [tab]: sortBy } + : { + tracks: ItemSortBy.Name, + albums: ItemSortBy.Name, + artists: ItemSortBy.SortName, + [tab]: sortBy, + } + return { sortBy: next } + }), + setSortDescending: (tab: LibraryTab, sortDescending: boolean) => + set((state) => { + const current = state.sortDescending as SortOrderState | boolean + const next: SortOrderState = + typeof current === 'object' && current !== null && 'tracks' in current + ? { ...current, [tab]: sortDescending } + : { + tracks: false, + albums: false, + artists: false, + [tab]: sortDescending, + } + return { sortDescending: next } + }), + getSortBy: (tab: LibraryTab) => { + const sortBy = get().sortBy as SortState | string + if (typeof sortBy === 'string') return sortBy as ItemSortBy + return sortBy[tab] ?? ItemSortBy.Name + }, + getSortDescending: (tab: LibraryTab) => { + const sortDescending = get().sortDescending as SortOrderState | boolean + if (typeof sortDescending === 'boolean') return sortDescending + return sortDescending[tab] ?? false + }, filters: { tracks: { diff --git a/src/utils/query-selectors.ts b/src/utils/query-selectors.ts index b62ea49a..f95561d5 100644 --- a/src/utils/query-selectors.ts +++ b/src/utils/query-selectors.ts @@ -1,15 +1,26 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto' +import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by' import { InfiniteData } from '@tanstack/react-query' import { isString } from 'lodash' import { RefObject } from 'react' +export type FlattenInfiniteQueryPagesOptions = { + /** + * When ItemSortBy.Artist, section letters are derived from the item's artist (AlbumArtist/Artists). + * When ItemSortBy.Album, section letters are derived from the item's album name. + * Otherwise (Name, SortName, etc.) letters are derived from the item's name/SortName. + */ + sortBy?: ItemSortBy +} + export default function flattenInfiniteQueryPages( data: InfiniteData, pageParams: RefObject>, + options?: FlattenInfiniteQueryPagesOptions, ) { /** - * A flattened array of all artists derived from the infinite query + * A flattened array of all items derived from the infinite query */ const flattenedItemPages = data.pages.flatMap((page) => page) @@ -19,15 +30,23 @@ export default function flattenInfiniteQueryPages( const seenLetters = new Set() /** - * The final array that will be provided to and rendered by the {@link Artists} component + * The final array that will be provided to and rendered by the list component */ const flashListItems: (string | number | BaseItemDto)[] = [] + // Letter source: Artist → artist; Album → album name; otherwise → item name (track name, etc.) + const extractLetter = + options?.sortBy === ItemSortBy.Artist + ? extractFirstLetterByArtist + : options?.sortBy === ItemSortBy.Album + ? extractFirstLetterByAlbum + : extractFirstLetter + flattenedItemPages.forEach((item: BaseItemDto) => { - const rawLetter = extractFirstLetter(item) + const rawLetter = extractLetter(item) /** - * An alpha character or a hash if the artist's name doesn't start with a letter + * An alpha character or a hash if the name doesn't start with a letter */ const letter = rawLetter.match(/[A-Z]/) ? rawLetter : '#' @@ -53,3 +72,17 @@ function extractFirstLetter({ Type, SortName, Name }: BaseItemDto): string { return letter } + +function extractFirstLetterByArtist(item: BaseItemDto): string { + const raw = + (isString(item.AlbumArtist) && item.AlbumArtist.trim()) || + (item.Artists?.[0] && isString(item.Artists[0]) && item.Artists[0].trim()) + if (!raw) return '#' + return raw.charAt(0).toUpperCase() +} + +function extractFirstLetterByAlbum(item: BaseItemDto): string { + const raw = isString(item.Album) && item.Album.trim() + if (!raw) return '#' + return raw.charAt(0).toUpperCase() +} From bfcae420c820a3500e6af23f5b9939e19201c786 Mon Sep 17 00:00:00 2001 From: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:47:19 -0600 Subject: [PATCH 3/5] update README Roadmap --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 314c6b11..16ae9693 100644 --- a/README.md +++ b/README.md @@ -188,16 +188,15 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility ### Roadmap #### 1.1.0 (Socket To Me Baby) - March '26 -- Android Auto/CarPlay Support -- Websocket Support (Server online status) -- Home Screen Updates -- Discover Screen Updates -- Artist Screen Redesign -- Library Redesign +- Gapless Playback +- WebSocket Support (Server online status) +- Library Enhancements - Quick Connect Support - Allow Self-Signed Certificates #### 1.2.0 (We Made a Language For Us Two...) - June '26 +- Android Auto/CarPlay Support +- EQ Controls - Collaborative Playlists - App Customization Options - Desktop Support (Experimental) @@ -207,12 +206,10 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility - Tablet Support #### 2.0.0 - December '26 -- Gapless Playback - Seerr (formerly Jellyseerr) Integration - JellyJam -- EQ Controls -#### 3.0.0 - TBD +#### 3.0.0 - December '27 - Watch Support - tvOS (Apple and Android) From 04e5eda5741a0ed32d3be1e08e0d2d639d13a848 Mon Sep 17 00:00:00 2001 From: Stephen Arg Date: Thu, 5 Feb 2026 19:51:13 +0100 Subject: [PATCH 4/5] Added sorting, additional filters, and made some adjustments to how rows present during specific sorting options (#981) Co-authored-by: StephenArg Co-authored-by: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com> --- src/api/queries/album/index.ts | 6 + src/api/queries/album/utils/album.ts | 16 +- src/api/queries/track/index.ts | 84 ++++- src/api/queries/track/keys.ts | 4 + src/api/queries/track/utils/index.ts | 6 + src/api/queries/years/index.ts | 27 ++ src/api/queries/years/keys.ts | 5 + src/api/queries/years/utils/index.ts | 36 +++ src/components/Albums/component.tsx | 6 +- src/components/Filters/index.tsx | 44 ++- .../Global/components/Track/index.tsx | 6 +- src/components/Global/components/item-row.tsx | 17 +- src/components/Library/tab-bar.tsx | 14 +- src/components/SortOptions/index.tsx | 1 - src/components/Tracks/component.tsx | 1 + src/hooks/player/functions/shuffle.ts | 26 ++ src/screens/GenreSelection/index.tsx | 39 +++ src/screens/YearSelection/index.tsx | 295 ++++++++++++++++++ src/screens/index.tsx | 11 + src/screens/types.d.ts | 2 + src/stores/library.ts | 6 + src/types/JellifyTrack.ts | 1 + src/utils/formatting/artist-names.ts | 12 +- src/utils/mapping/build-years-param.ts | 9 + 24 files changed, 643 insertions(+), 31 deletions(-) create mode 100644 src/api/queries/years/index.ts create mode 100644 src/api/queries/years/keys.ts create mode 100644 src/api/queries/years/utils/index.ts create mode 100644 src/screens/YearSelection/index.tsx create mode 100644 src/utils/mapping/build-years-param.ts diff --git a/src/api/queries/album/index.ts b/src/api/queries/album/index.ts index 0c74a34d..5af3fe55 100644 --- a/src/api/queries/album/index.ts +++ b/src/api/queries/album/index.ts @@ -48,6 +48,8 @@ const useAlbums: () => [ : ItemSortBy.Album const sortDescending = librarySortDescendingState.albums ?? false const isFavorites = filters.albums.isFavorites + const yearMin = filters.albums.yearMin + const yearMax = filters.albums.yearMax const albumPageParams = useRef>(new Set()) @@ -72,6 +74,8 @@ const useAlbums: () => [ library?.musicLibraryId, librarySortBy, sortDescending, + yearMin, + yearMax, ], queryFn: ({ pageParam }) => fetchAlbums( @@ -82,6 +86,8 @@ const useAlbums: () => [ isFavorites, [librarySortBy ?? ItemSortBy.SortName], [sortDescending ? SortOrder.Descending : SortOrder.Ascending], + yearMin, + yearMax, ), initialPageParam: 0, select: selectAlbums, diff --git a/src/api/queries/album/utils/album.ts b/src/api/queries/album/utils/album.ts index 44b05b72..668dfa7a 100644 --- a/src/api/queries/album/utils/album.ts +++ b/src/api/queries/album/utils/album.ts @@ -9,9 +9,9 @@ import { JellifyLibrary } from '../../../../types/JellifyLibrary' import { Api } from '@jellyfin/sdk' import { fetchItem, fetchItems } from '../../item' import { JellifyUser } from '../../../../types/JellifyUser' -import { getItemsApi } from '@jellyfin/sdk/lib/utils/api' import { ApiLimits } from '../../../../configs/query.config' import { nitroFetch } from '../../../utils/nitro' +import buildYearsParam from '../../../../utils/mapping/build-years-param' export function fetchAlbums( api: Api | undefined, @@ -21,12 +21,16 @@ export function fetchAlbums( isFavorite: boolean | undefined, sortBy: ItemSortBy[] = [ItemSortBy.SortName], sortOrder: SortOrder[] = [SortOrder.Ascending], + yearMin?: number, + yearMax?: number, ): Promise { return new Promise((resolve, reject) => { if (!api) return reject('No API instance provided') if (!user) return reject('No user provided') if (!library) return reject('Library has not been set') + const yearsParam = buildYearsParam(yearMin, yearMax) + nitroFetch<{ Items: BaseItemDto[] }>(api, '/Items', { ParentId: library.musicLibraryId, IncludeItemTypes: [BaseItemKind.MusicAlbum], @@ -38,9 +42,15 @@ export function fetchAlbums( IsFavorite: isFavorite, Fields: [ItemFields.SortName], Recursive: true, - }).then((data) => { - return data.Items ? resolve(data.Items) : resolve([]) + Years: yearsParam, }) + .then((data) => { + return data.Items ? resolve(data.Items) : resolve([]) + }) + .catch((error) => { + console.error(error) + return reject(error) + }) }) } diff --git a/src/api/queries/track/index.ts b/src/api/queries/track/index.ts index c9c0f586..617897f1 100644 --- a/src/api/queries/track/index.ts +++ b/src/api/queries/track/index.ts @@ -44,6 +44,8 @@ const useTracks: ( const isDownloaded = filters.tracks.isDownloaded ?? false const isLibraryUnplayed = filters.tracks.isUnplayed ?? false const libraryGenreIds = filters.tracks.genreIds + const libraryYearMin = filters.tracks.yearMin + const libraryYearMax = filters.tracks.yearMax // Use provided values or fallback to library context // If artistId is present, we use isFavoritesParam if provided, otherwise false (default to showing all artist tracks) @@ -95,6 +97,8 @@ const useTracks: ( finalSortBy, finalSortOrder, isDownloaded ? undefined : libraryGenreIds, + libraryYearMin, + libraryYearMax, ), queryFn: ({ pageParam }) => { if (!isDownloaded) { @@ -109,21 +113,33 @@ const useTracks: ( finalSortOrder, artistId, libraryGenreIds, + libraryYearMin, + libraryYearMax, ) - } else - return (downloadedTracks ?? []) - .map(({ item }) => item) - .sort((a, b) => { - const aName = a.Name ?? '' - const bName = b.Name ?? '' - if (aName < bName) return -1 - else if (aName === bName) return 0 - else return 1 - }) - .filter((track) => { - if (!isFavorites) return true - else return isDownloadedTrackAlsoFavorite(user, track) + } else { + let items = (downloadedTracks ?? []).map(({ item }) => item) + if (libraryYearMin != null || libraryYearMax != null) { + const min = libraryYearMin ?? 0 + const max = libraryYearMax ?? new Date().getFullYear() + items = items.filter((track) => { + const y = + 'ProductionYear' in track + ? (track as BaseItemDto).ProductionYear + : undefined + if (y == null) return false + return y >= min && y <= max }) + } + const sortByForCompare = + finalSortBy === ItemSortBy.SortName ? ItemSortBy.Name : finalSortBy + items = items.sort((a, b) => + compareDownloadedTracks(a, b, sortByForCompare, finalSortOrder), + ) + return items.filter((track) => { + if (!isFavorites) return true + else return isDownloadedTrackAlsoFavorite(user, track) + }) + } }, initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => { @@ -147,3 +163,45 @@ function isDownloadedTrackAlsoFavorite(user: JellifyUser | undefined, track: Bas return userData?.IsFavorite ?? false } + +function getSortValue(item: BaseItemDto, sortBy: ItemSortBy): string | number { + switch (sortBy) { + case ItemSortBy.Name: + case ItemSortBy.SortName: + return item.Name ?? item.SortName ?? '' + case ItemSortBy.Album: + return item.Album ?? '' + case ItemSortBy.Artist: + return item.AlbumArtist ?? item.Artists?.[0] ?? '' + case ItemSortBy.DateCreated: + return item.DateCreated ? new Date(item.DateCreated).getTime() : 0 + case ItemSortBy.PlayCount: + return item.UserData?.PlayCount ?? 0 + case ItemSortBy.PremiereDate: + return item.PremiereDate ? new Date(item.PremiereDate).getTime() : 0 + case ItemSortBy.Runtime: + return item.RunTimeTicks ?? 0 + default: + return item.Name ?? item.SortName ?? '' + } +} + +function compareDownloadedTracks( + a: BaseItemDto, + b: BaseItemDto, + sortBy: ItemSortBy, + sortOrder: SortOrder, +): number { + const aVal = getSortValue(a, sortBy) + const bVal = getSortValue(b, sortBy) + const isDesc = sortOrder === SortOrder.Descending + let cmp: number + if (typeof aVal === 'number' && typeof bVal === 'number') { + cmp = aVal - bVal + } else { + const aStr = String(aVal) + const bStr = String(bVal) + cmp = aStr.localeCompare(bStr, undefined, { sensitivity: 'base' }) + } + return isDesc ? -cmp : cmp +} diff --git a/src/api/queries/track/keys.ts b/src/api/queries/track/keys.ts index 219598e0..616fe657 100644 --- a/src/api/queries/track/keys.ts +++ b/src/api/queries/track/keys.ts @@ -16,6 +16,8 @@ export const TracksQueryKey = ( sortBy?: string, sortOrder?: string, genreIds?: string[], + yearMin?: number, + yearMax?: number, ) => [ TrackQueryKeys.AllTracks, library?.musicLibraryId, @@ -27,4 +29,6 @@ export const TracksQueryKey = ( sortBy, sortOrder, genreIds && genreIds.length > 0 ? `genres:${genreIds.sort().join(',')}` : undefined, + yearMin, + yearMax, ] diff --git a/src/api/queries/track/utils/index.ts b/src/api/queries/track/utils/index.ts index d308b6c7..61a878ce 100644 --- a/src/api/queries/track/utils/index.ts +++ b/src/api/queries/track/utils/index.ts @@ -12,6 +12,7 @@ import { nitroFetch } from '../../../utils/nitro' import { isUndefined } from 'lodash' import { ApiLimits } from '../../../../configs/query.config' import { JellifyUser } from '../../../../types/JellifyUser' +import buildYearsParam from '../../../../utils/mapping/build-years-param' export default function fetchTracks( api: Api | undefined, @@ -24,6 +25,8 @@ export default function fetchTracks( sortOrder: SortOrder = SortOrder.Ascending, artistId?: string, genreIds?: string[], + yearMin?: number, + yearMax?: number, ) { return new Promise((resolve, reject) => { if (isUndefined(api)) return reject('Client instance not set') @@ -43,6 +46,8 @@ export default function fetchTracks( filters.push(ItemFilter.IsUnplayed) } + const yearsParam = buildYearsParam(yearMin, yearMax) + nitroFetch<{ Items: BaseItemDto[] }>(api, '/Items', { IncludeItemTypes: [BaseItemKind.Audio], ParentId: library.musicLibraryId, @@ -56,6 +61,7 @@ export default function fetchTracks( Fields: [ItemFields.SortName], ArtistIds: artistId ? [artistId] : undefined, GenreIds: genreIds && genreIds.length > 0 ? genreIds : undefined, + Years: yearsParam, }) .then((data) => { if (data.Items) return resolve(data.Items) diff --git a/src/api/queries/years/index.ts b/src/api/queries/years/index.ts new file mode 100644 index 00000000..c9951ff9 --- /dev/null +++ b/src/api/queries/years/index.ts @@ -0,0 +1,27 @@ +import { useQuery } from '@tanstack/react-query' +import { fetchLibraryYears } from './utils' +import { LibraryYearsQueryKey } from './keys' +import { getApi, getUser, useJellifyLibrary } from '../../../stores' + +export function useLibraryYears(): { + years: number[] + isPending: boolean + isError: boolean +} { + const api = getApi() + const user = getUser() + const [library] = useJellifyLibrary() + + const { + data: years = [], + isPending, + isError, + } = useQuery({ + queryKey: LibraryYearsQueryKey(library?.musicLibraryId, user?.id), + queryFn: () => fetchLibraryYears(api, library, user?.id), + enabled: Boolean(api && library && user?.id), + staleTime: 5 * 60 * 1000, + }) + + return { years, isPending, isError } +} diff --git a/src/api/queries/years/keys.ts b/src/api/queries/years/keys.ts new file mode 100644 index 00000000..869f4c8d --- /dev/null +++ b/src/api/queries/years/keys.ts @@ -0,0 +1,5 @@ +export const LibraryYearsQueryKey = (libraryId: string | undefined, userId: string | undefined) => [ + 'LibraryYears', + libraryId, + userId, +] diff --git a/src/api/queries/years/utils/index.ts b/src/api/queries/years/utils/index.ts new file mode 100644 index 00000000..b4505a3e --- /dev/null +++ b/src/api/queries/years/utils/index.ts @@ -0,0 +1,36 @@ +import { Api } from '@jellyfin/sdk' +import { JellifyLibrary } from '../../../../types/JellifyLibrary' +import { nitroFetch } from '../../../utils/nitro' +import { isUndefined } from 'lodash' +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models' + +export type ItemsFiltersResponse = { + Genres?: string[] | null + Years?: number[] | null + Tags?: string[] | null + OfficialRatings?: string[] | null +} + +/** + * Fetches available filter values (genres, years) for the music library via /Items/Filters. + * Uses MusicAlbum so years reflect album release dates in the library. + * Returns sorted ascending list of year numbers. + */ +export async function fetchLibraryYears( + api: Api | undefined, + library: JellifyLibrary | undefined, + userId: string | undefined, +): Promise { + if (isUndefined(api)) throw new Error('Client instance not set') + if (isUndefined(library)) throw new Error('Library instance not set') + if (isUndefined(userId)) throw new Error('User id required') + + const data = await nitroFetch(api, '/Items/Filters', { + UserId: userId, + ParentId: library.musicLibraryId, + IncludeItemTypes: [BaseItemKind.MusicAlbum], + }) + + const years = data?.Years ?? [] + return [...years].filter((y) => typeof y === 'number' && !Number.isNaN(y)).sort((a, b) => a - b) +} diff --git a/src/components/Albums/component.tsx b/src/components/Albums/component.tsx index d32aab73..a5b39201 100644 --- a/src/components/Albums/component.tsx +++ b/src/components/Albums/component.tsx @@ -77,7 +77,11 @@ export default function Albums({ ) ) : typeof album === 'number' ? null : typeof album === 'object' ? ( - + ) : null const onEndReached = () => { diff --git a/src/components/Filters/index.tsx b/src/components/Filters/index.tsx index 4aa32347..566f1851 100644 --- a/src/components/Filters/index.tsx +++ b/src/components/Filters/index.tsx @@ -8,7 +8,6 @@ import { FiltersProps } from './types' import Icon from '../Global/components/icon' import { NativeStackNavigationProp } from '@react-navigation/native-stack' import { RootStackParamList } from '../../screens/types' -import { trigger } from 'react-native-haptic-feedback' export default function Filters({ currentTab, @@ -27,6 +26,11 @@ export default function Filters({ const isUnplayed = currentFilters.isUnplayed ?? false const selectedGenreIds = currentFilters.genreIds ?? [] const hasGenresSelected = selectedGenreIds.length > 0 + const yearMin = currentFilters.yearMin + const yearMax = currentFilters.yearMax + const hasYearRange = yearMin != null || yearMax != null + const yearRangeLabel = + yearMin != null || yearMax != null ? `${yearMin ?? '…'} – ${yearMax ?? '…'}` : null const handleFavoritesToggle = (checked: boolean | 'indeterminate') => { triggerHaptic('impactLight') @@ -60,6 +64,13 @@ export default function Filters({ navigation?.navigate('GenreSelection') } + const handleYearRangeSelect = () => { + triggerHaptic('impactLight') + navigation?.navigate('YearSelection', { + tab: currentTab === 'Tracks' || currentTab === 'Albums' ? currentTab : 'Tracks', + }) + } + const handleUnplayedToggle = (checked: boolean | 'indeterminate') => { triggerHaptic('impactLight') if (currentTab === 'Tracks') { @@ -144,6 +155,37 @@ export default function Filters({ )} + + {(isTracksTab || currentTab === 'Albums') && ( + + + + )} ) diff --git a/src/components/Global/components/Track/index.tsx b/src/components/Global/components/Track/index.tsx index 089e6484..2bd68d07 100644 --- a/src/components/Global/components/Track/index.tsx +++ b/src/components/Global/components/Track/index.tsx @@ -37,6 +37,7 @@ export interface TrackProps { editing?: boolean | undefined sortingByAlbum?: boolean | undefined sortingByReleasedDate?: boolean | undefined + sortingByPlayCount?: boolean | undefined } export default function Track({ @@ -54,6 +55,7 @@ export default function Track({ editing, sortingByAlbum, sortingByReleasedDate, + sortingByPlayCount, }: TrackProps): React.JSX.Element { const theme = useTheme() const [artworkAreaWidth, setArtworkAreaWidth] = useState(0) @@ -141,7 +143,9 @@ export default function Track({ ? track.Album : sortingByReleasedDate ? `${track.ProductionYear?.toString()} • ${track.Artists?.join(' • ')}` - : track.Artists?.join(' • ')) ?? '' + : sortingByPlayCount + ? `${track.UserData?.PlayCount?.toString()} • ${track.Artists?.join(' • ')}` + : track.Artists?.join(' • ')) ?? '' // Memoize track name const trackName = track.Name ?? 'Untitled Track' diff --git a/src/components/Global/components/item-row.tsx b/src/components/Global/components/item-row.tsx index 3cc0e6cb..f9a10968 100644 --- a/src/components/Global/components/item-row.tsx +++ b/src/components/Global/components/item-row.tsx @@ -38,6 +38,7 @@ interface ItemRowProps { onLongPress?: () => void navigation?: Pick, 'navigate' | 'dispatch'> queueName?: Queue + sortingByReleasedDate?: boolean | undefined } /** @@ -58,6 +59,7 @@ function ItemRow({ onPress, onLongPress, queueName, + sortingByReleasedDate, }: ItemRowProps): React.JSX.Element { const artworkAreaWidth = useSharedValue(0) @@ -170,7 +172,7 @@ function ItemRow({ > - + @@ -235,7 +237,13 @@ function ItemRow({ ) } -function ItemRowDetails({ item }: { item: BaseItemDto }): React.JSX.Element { +function ItemRowDetails({ + item, + sortingByReleasedDate, +}: { + item: BaseItemDto + sortingByReleasedDate?: boolean | undefined +}): React.JSX.Element { const route = useRoute>() const shouldRenderArtistName = @@ -253,7 +261,10 @@ function ItemRowDetails({ item }: { item: BaseItemDto }): React.JSX.Element { {shouldRenderArtistName && ( - {formatArtistName(item.AlbumArtist)} + {formatArtistName( + item.AlbumArtist, + sortingByReleasedDate ? item.ProductionYear?.toString() : undefined, + )} )} diff --git a/src/components/Library/tab-bar.tsx b/src/components/Library/tab-bar.tsx index 1f4e02cc..c7e44106 100644 --- a/src/components/Library/tab-bar.tsx +++ b/src/components/Library/tab-bar.tsx @@ -32,7 +32,9 @@ function LibraryTabBar(props: MaterialTopTabBarProps) { (currentFilters.isFavorites === true || currentFilters.isDownloaded === true || currentFilters.isUnplayed === true || - (currentFilters.genreIds && currentFilters.genreIds.length > 0)) + (currentFilters.genreIds && currentFilters.genreIds.length > 0) || + currentFilters.yearMin != null || + currentFilters.yearMax != null) const handleShufflePress = async () => { triggerHaptic('impactLight') @@ -163,11 +165,15 @@ function LibraryTabBar(props: MaterialTopTabBarProps) { isDownloaded: false, isUnplayed: false, genreIds: undefined, + yearMin: undefined, + yearMax: undefined, }) } else if (currentTab === 'Albums') { - useLibraryStore - .getState() - .setAlbumsFilters({ isFavorites: undefined }) + useLibraryStore.getState().setAlbumsFilters({ + isFavorites: undefined, + yearMin: undefined, + yearMax: undefined, + }) } else if (currentTab === 'Artists') { useLibraryStore .getState() diff --git a/src/components/SortOptions/index.tsx b/src/components/SortOptions/index.tsx index 9361bb1a..77dd30c5 100644 --- a/src/components/SortOptions/index.tsx +++ b/src/components/SortOptions/index.tsx @@ -20,7 +20,6 @@ const TRACK_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [ const ALBUM_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [ { value: ItemSortBy.SortName, label: 'Album' }, { value: ItemSortBy.Artist, label: 'Artist' }, - { value: ItemSortBy.PlayCount, label: 'Play Count' }, { value: ItemSortBy.DateCreated, label: 'Date Added' }, { value: ItemSortBy.PremiereDate, label: 'Release Date' }, ] diff --git a/src/components/Tracks/component.tsx b/src/components/Tracks/component.tsx index 546e1a82..c00f00bd 100644 --- a/src/components/Tracks/component.tsx +++ b/src/components/Tracks/component.tsx @@ -96,6 +96,7 @@ export default function Tracks({ queue={queue} sortingByAlbum={sortBy === ItemSortBy.Album} sortingByReleasedDate={sortBy === ItemSortBy.PremiereDate} + sortingByPlayCount={sortBy === ItemSortBy.PlayCount} /> ) : ( diff --git a/src/hooks/player/functions/shuffle.ts b/src/hooks/player/functions/shuffle.ts index fe10945a..e6052971 100644 --- a/src/hooks/player/functions/shuffle.ts +++ b/src/hooks/player/functions/shuffle.ts @@ -67,6 +67,8 @@ export async function handleShuffle(keepCurrentTrack: boolean = true): Promise { + const y = download.item.ProductionYear + return y != null && y >= min && y <= max + }) + } + // Filter by favorites if (isFavorites) { filteredDownloads = filteredDownloads.filter((download) => { @@ -118,6 +130,19 @@ export async function handleShuffle(keepCurrentTrack: boolean = true): Promise { + const min = yearMin ?? 0 + const max = yearMax ?? new Date().getFullYear() + if (min > max) return undefined + const years: string[] = [] + for (let y = min; y <= max; y++) years.push(String(y)) + return years.length > 0 ? years : undefined + })() + : undefined + // Fetch random tracks from Jellyfin with filters const data = await nitroFetch<{ Items: BaseItemDto[] }>(api, '/Items', { ParentId: library.musicLibraryId, @@ -127,6 +152,7 @@ export async function handleShuffle(keepCurrentTrack: boolean = true): Promise 0 ? apiFilters : undefined, GenreIds: genreIds && genreIds.length > 0 ? genreIds : undefined, + Years: yearsParam, Limit: ApiLimits.LibraryShuffle, Fields: [ ItemFields.MediaSources, diff --git a/src/screens/GenreSelection/index.tsx b/src/screens/GenreSelection/index.tsx index e5dcfa27..aaa9aaf5 100644 --- a/src/screens/GenreSelection/index.tsx +++ b/src/screens/GenreSelection/index.tsx @@ -94,6 +94,44 @@ export default function GenreSelectionScreen({ }) }, [triggerHaptic]) + const allLoadedGenreIds = useMemo( + () => genres?.map((g) => g.Id!).filter(Boolean) ?? [], + [genres], + ) + const allSelected = + allLoadedGenreIds.length > 0 && selectedGenreIds.length === allLoadedGenreIds.length + + const handleSelectAll = useCallback(() => { + triggerHaptic('impactLight') + setSelectedGenreIds([...allLoadedGenreIds]) + }, [allLoadedGenreIds, triggerHaptic]) + + const renderListHeader = useCallback( + () => ( + + + Select all + {genres != null && ( + {`${allLoadedGenreIds.length} genres`} + )} + + + + ), + [handleSelectAll, allSelected, allLoadedGenreIds.length, genres], + ) + const renderItem: ListRenderItem = ({ item }) => { if (typeof item === 'string') { // Section header @@ -181,6 +219,7 @@ export default function GenreSelectionScreen({ data={flattenedGenres} renderItem={renderItem} keyExtractor={keyExtractor} + ListHeaderComponent={renderListHeader} // @ts-expect-error - estimatedItemSize is required by FlashList but types are incorrect estimatedItemSize={70} onEndReached={() => { diff --git a/src/screens/YearSelection/index.tsx b/src/screens/YearSelection/index.tsx new file mode 100644 index 00000000..a8d5c13d --- /dev/null +++ b/src/screens/YearSelection/index.tsx @@ -0,0 +1,295 @@ +import React, { useCallback, useMemo, useState } from 'react' +import { YStack, XStack, Button, Spinner } from 'tamagui' +import { Modal, ScrollView, Pressable } from 'react-native' +import { Text } from '../../components/Global/helpers/text' +import Icon from '../../components/Global/components/icon' +import { triggerHaptic } from '../../hooks/use-haptic-feedback' +import { YearSelectionProps } from '../types' +import useLibraryStore from '../../stores/library' +import { useLibraryYears } from '../../api/queries/years' + +const ANY = 'any' +type Picking = 'min' | 'max' | null + +export default function YearSelectionScreen({ + navigation, + route, +}: YearSelectionProps): React.JSX.Element { + const tab = route.params?.tab ?? 'Tracks' + const { years: availableYears, isPending, isError } = useLibraryYears() + const storeFilters = useLibraryStore.getState().filters[tab === 'Albums' ? 'albums' : 'tracks'] + const [minYear, setMinYear] = useState(storeFilters.yearMin ?? ANY) + const [maxYear, setMaxYear] = useState(storeFilters.yearMax ?? ANY) + const [picking, setPicking] = useState(null) + + // Min year options: if maxYear is set, only years <= maxYear + const minYearOptions = useMemo(() => { + if (availableYears.length === 0) return [] + const max = typeof maxYear === 'number' ? maxYear : Math.max(...availableYears) + return availableYears.filter((y) => y <= max) + }, [availableYears, maxYear]) + + // Max year options: if minYear is set, only years >= minYear + const maxYearOptions = useMemo(() => { + if (availableYears.length === 0) return [] + const min = typeof minYear === 'number' ? minYear : Math.min(...availableYears) + return availableYears.filter((y) => y >= min) + }, [availableYears, minYear]) + + const handleOpenMin = useCallback(() => { + triggerHaptic('impactLight') + setPicking('min') + }, []) + + const handleOpenMax = useCallback(() => { + triggerHaptic('impactLight') + setPicking('max') + }, []) + + const handleSelectMin = useCallback( + (year: number | typeof ANY) => { + triggerHaptic('impactLight') + setMinYear(year) + setPicking(null) + if (year !== ANY && typeof maxYear === 'number' && year > maxYear) { + setMaxYear(year) + } + }, + [maxYear], + ) + + const handleSelectMax = useCallback( + (year: number | typeof ANY) => { + triggerHaptic('impactLight') + setMaxYear(year) + setPicking(null) + if (year !== ANY && typeof minYear === 'number' && year < minYear) { + setMinYear(year) + } + }, + [minYear], + ) + + const handleSave = useCallback(() => { + triggerHaptic('impactLight') + const payload = { + yearMin: minYear === ANY ? undefined : minYear, + yearMax: maxYear === ANY ? undefined : maxYear, + } + if (tab === 'Albums') { + useLibraryStore.getState().setAlbumsFilters(payload) + } else { + useLibraryStore.getState().setTracksFilters(payload) + } + navigation.goBack() + }, [minYear, maxYear, navigation, tab]) + + const handleClear = useCallback(() => { + triggerHaptic('impactLight') + setMinYear(ANY) + setMaxYear(ANY) + const payload = { yearMin: undefined, yearMax: undefined } + if (tab === 'Albums') { + useLibraryStore.getState().setAlbumsFilters(payload) + } else { + useLibraryStore.getState().setTracksFilters(payload) + } + }, [tab]) + + const hasSelection = minYear !== ANY || maxYear !== ANY + const rangeLabel = + minYear !== ANY || maxYear !== ANY + ? `${minYear === ANY ? '…' : minYear} – ${maxYear === ANY ? '…' : maxYear}` + : null + + const minLabel = minYear === ANY ? 'Any' : String(minYear) + const maxLabel = maxYear === ANY ? 'Any' : String(maxYear) + + if (isPending && availableYears.length === 0) { + return ( + + + + ) + } + + if (isError) { + return ( + + Could not load years + + + ) + } + + const pickerOptions = picking === 'min' ? minYearOptions : maxYearOptions + const onSelectOption = picking === 'min' ? handleSelectMin : handleSelectMax + const currentValue = picking === 'min' ? minYear : maxYear + + return ( + + + + + Year range + + + + + + + Min year + + + + {minLabel} + + + + + + Max year + + + + {maxLabel} + + + + + + {/* Dropdown picker modal */} + setPicking(null)} + > + setPicking(null)} + > + e.stopPropagation()}> + + + {picking === 'min' ? 'Select min year' : 'Select max year'} + + + onSelectOption(ANY)} + style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} + > + + + Any + + + + {pickerOptions.map((y) => ( + onSelectOption(y)} + style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} + > + + + {String(y)} + + + + ))} + + + + + + + {hasSelection && ( + + + {rangeLabel ?? ''} + + + + )} + + ) +} diff --git a/src/screens/index.tsx b/src/screens/index.tsx index 38947c43..261899ca 100644 --- a/src/screens/index.tsx +++ b/src/screens/index.tsx @@ -19,6 +19,7 @@ import { formatArtistNames } from '../utils/formatting/artist-names' import FiltersSheet from './Filters' import SortOptionsSheet from './SortOptions' import GenreSelectionScreen from './GenreSelection' +import YearSelectionScreen from './YearSelection' const RootStack = createNativeStackNavigator() @@ -132,6 +133,16 @@ export default function Root(): React.JSX.Element { sheetGrabberVisible: true, }} /> + + ) } diff --git a/src/screens/types.d.ts b/src/screens/types.d.ts index 49e01c98..5be45316 100644 --- a/src/screens/types.d.ts +++ b/src/screens/types.d.ts @@ -74,6 +74,7 @@ export type RootStackParamList = { } GenreSelection: undefined + YearSelection: { tab?: 'Tracks' | 'Albums' } AudioSpecs: { item: BaseItemDto @@ -99,6 +100,7 @@ export type DeletePlaylistProps = NativeStackScreenProps export type SortOptionsProps = NativeStackScreenProps export type GenreSelectionProps = NativeStackScreenProps +export type YearSelectionProps = NativeStackScreenProps export type GenresProps = { genres: InfiniteData | undefined diff --git a/src/stores/library.ts b/src/stores/library.ts index 08465e33..57c16914 100644 --- a/src/stores/library.ts +++ b/src/stores/library.ts @@ -10,6 +10,8 @@ type TabFilterState = { isDownloaded?: boolean // Only for Tracks tab isUnplayed?: boolean // Only for Tracks tab genreIds?: string[] // Only for Tracks tab + yearMin?: number // Tracks and Albums + yearMax?: number // Tracks and Albums } type SortState = Record @@ -93,9 +95,13 @@ const useLibraryStore = create()( isDownloaded: false, isUnplayed: undefined, genreIds: undefined, + yearMin: undefined, + yearMax: undefined, }, albums: { isFavorites: undefined, + yearMin: undefined, + yearMax: undefined, }, artists: { isFavorites: undefined, diff --git a/src/types/JellifyTrack.ts b/src/types/JellifyTrack.ts index cede2e04..c86918b6 100644 --- a/src/types/JellifyTrack.ts +++ b/src/types/JellifyTrack.ts @@ -16,6 +16,7 @@ export type BaseItemDtoSlimified = Pick< | 'RunTimeTicks' | 'OfficialRating' | 'CustomRating' + | 'ProductionYear' > interface JellifyTrack extends Track { diff --git a/src/utils/formatting/artist-names.ts b/src/utils/formatting/artist-names.ts index eee75ab3..dc496b9d 100644 --- a/src/utils/formatting/artist-names.ts +++ b/src/utils/formatting/artist-names.ts @@ -1,9 +1,13 @@ -export function formatArtistName(artistName: string | null | undefined): string { - if (!artistName) return 'Unknown Artist' - return artistName +export function formatArtistName( + artistName: string | null | undefined, + releaseDate?: string | null | undefined, +): string { + const unknownArtist = 'Unknown Artist' + if (!artistName) return releaseDate ? `${releaseDate} • ${unknownArtist}` : unknownArtist + return releaseDate ? `${releaseDate} • ${artistName}` : artistName } export function formatArtistNames(artistNames: string[] | null | undefined): string { if (!artistNames || artistNames.length === 0) return 'Unknown Artist' - return artistNames.map(formatArtistName).join(' • ') + return artistNames.map((artistName) => formatArtistName(artistName)).join(' • ') } diff --git a/src/utils/mapping/build-years-param.ts b/src/utils/mapping/build-years-param.ts new file mode 100644 index 00000000..13fc5a1b --- /dev/null +++ b/src/utils/mapping/build-years-param.ts @@ -0,0 +1,9 @@ +export default function buildYearsParam(yearMin?: number, yearMax?: number): string[] | undefined { + if (yearMin == null && yearMax == null) return undefined + const min = yearMin ?? 0 + const max = yearMax ?? new Date().getFullYear() + if (min > max) return undefined + const years: string[] = [] + for (let y = min; y <= max; y++) years.push(String(y)) + return years.length > 0 ? years : undefined +} From 80d2a4a3ec5cf438ac967670344946b75921f6f5 Mon Sep 17 00:00:00 2001 From: Stephen Arg Date: Fri, 6 Feb 2026 07:03:04 +0100 Subject: [PATCH 5/5] Made sure explicit icon shows up on album page as well (#983) Co-authored-by: StephenArg --- .../Global/components/Track/content.tsx | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/components/Global/components/Track/content.tsx b/src/components/Global/components/Track/content.tsx index be89e6e8..fc350f78 100644 --- a/src/components/Global/components/Track/content.tsx +++ b/src/components/Global/components/Track/content.tsx @@ -142,15 +142,26 @@ export default function TrackRowContent({ - - {trackName} - + + + {trackName} + + {!shouldShowArtists && isExplicit(track as JellifyTrack) && ( + + + + )} + {shouldShowArtists && (