From 06b7bda3cc04b76c62715e537c371ad16869ec8b Mon Sep 17 00:00:00 2001 From: Dan Stepanov Date: Fri, 11 Sep 2026 16:42:45 -0700 Subject: [PATCH 1/3] fix: prepare Expo 57 compatibility for RC audit --- .config/jest.setup.js | 8 + .github/workflows/ci.yml | 3 + css.d.ts | 1 + docs/css-variable-inputs.md | 22 + docs/known-issues.md | 9 + docs/v5-engine-contracts.md | 11 + example/example-env.d.ts | 58 +- example/package.json | 22 +- node/__tests__/cache-version.mjs | 99 + node/__tests__/component-registry.mjs | 115 + node/__tests__/lightningcss-loader.mjs | 139 + node/__tests__/tooling.mjs | 53 + node/__tests__/typescript.mjs | 173 ++ node/babel.mjs | 4 + node/compiler.mjs | 2 + node/metro.mjs | 2 + package.json | 72 +- src/__fixtures__/types/component-mapping.tsx | 21 + .../babel/{plugin.test.mts => plugin.test.ts} | 19 +- src/__tests__/babel/react-native-web.test.ts | 45 +- src/__tests__/babel/react-native.test.ts | 29 +- src/__tests__/babel/runtime-interop.test.ts | 118 + src/__tests__/babel/smoke.test.ts | 6 +- src/__tests__/babel/tsconfig.json | 6 +- src/__tests__/compiler/compiler.test.tsx | 194 +- .../compiler/logical-borders.test.ts | 164 ++ src/__tests__/compiler/media-query.test.ts | 74 +- src/__tests__/native/_styled-types.tsx | 98 + .../animation-cancellation-contract.test.tsx | 50 + src/__tests__/native/animations.test.tsx | 334 ++- src/__tests__/native/attributes.test.tsx | 147 + src/__tests__/native/calc.test.tsx | 22 + src/__tests__/native/color-mix.test.tsx | 49 + src/__tests__/native/components.test.tsx | 37 + .../native/container-boundaries.test.tsx | 32 + .../native/container-rejection.test.tsx | 56 + src/__tests__/native/direction-media.test.tsx | 35 + .../native/expo57-regressions.test.tsx | 168 ++ .../native/filter-list-runtime.test.tsx | 74 + .../native/gradient-contract.test.tsx | 57 + .../native/group-attributes.test.tsx | 76 + src/__tests__/native/grouping.test.tsx | 54 +- .../native/image-background.test.tsx | 26 + src/__tests__/native/image-fit.test.tsx | 87 + .../native/logical-border-runtime.test.tsx | 63 + .../native/logical-inset-contract.test.tsx | 19 + .../native/media-query-union.test.tsx | 59 + src/__tests__/native/prop-mapping.test.tsx | 65 + src/__tests__/native/pseudo-classes.test.tsx | 73 + .../native/reactivity-dependencies.test.tsx | 106 + .../native/reactivity-lifecycle.test.tsx | 172 ++ src/__tests__/native/selectors.test.tsx | 121 +- src/__tests__/native/styled.test.ios.tsx | 262 +- src/__tests__/native/transform.test.tsx | 2 +- src/__tests__/native/transitions.test.tsx | 185 +- .../native/universal-variables.test.tsx | 94 + src/__tests__/native/variable-cycles.test.tsx | 49 + .../vendor/tailwind/backgrounds.test.tsx | 2 +- .../vendor/tailwind/borders.test.tsx | 31 +- .../vendor/tailwind/filters.test.tsx | 30 +- .../vendor/tailwind/transform.test.ts | 8 +- src/__tests__/web/mapping.test.tsx | 91 + src/babel/import-plugin.ts | 19 +- src/babel/react-native-web.ts | 35 +- src/babel/react-native.ts | 8 +- src/compiler/atRules.ts | 44 + src/compiler/compiler.ts | 104 +- src/compiler/container-query.ts | 11 +- src/compiler/declarations.ts | 240 +- src/compiler/lightningcss-loader.ts | 31 +- src/compiler/media-query.ts | 29 +- src/compiler/selector-builder.ts | 32 +- src/components/ImageBackground.tsx | 8 +- src/components/KeyboardAvoidingView.tsx | 5 +- src/components/index.cts | 47 +- src/jest/index.ts | 2 +- src/metro/cache-version.ts | 48 + src/metro/index.ts | 2 + src/metro/metro-transformer.ts | 5 +- src/metro/typescript.ts | 59 +- src/native-internal/style-collection.ts | 2 +- src/native/api.tsx | 34 +- src/native/conditions/attributes.ts | 35 +- src/native/conditions/container-query.ts | 20 +- src/native/conditions/media-query.ts | 2 +- src/native/react/interaction.ts | 17 +- src/native/react/rules.ts | 18 +- src/native/react/useNativeCss.ts | 67 +- src/native/react/usePassthrough.ts | 5 +- src/native/reactivity.ts | 66 +- src/native/reanimated.ts | 29 +- src/native/styles/functions/color-mix.ts | 84 +- src/native/styles/functions/filters.ts | 14 + .../styles/functions/numeric-functions.ts | 13 +- .../styles/functions/string-functions.ts | 18 + .../styles/functions/transform-functions.ts | 9 +- src/native/styles/index.ts | 70 +- src/native/styles/resolve.ts | 26 +- src/native/styles/scale-factor.ts | 6 + src/native/styles/shorthands/animation.ts | 10 +- src/native/styles/shorthands/index.ts | 1 + .../styles/shorthands/logical-border-width.ts | 32 + src/native/styles/variables.ts | 86 +- src/runtime.types.ts | 32 +- src/utilities/dot-notation.types.ts | 7 +- src/utilities/style-descriptor.ts | 27 +- src/web/api.tsx | 17 +- src/web/assign-style.ts | 3 +- tsconfig.build.json | 11 + tsconfig.json | 3 +- types.d.ts | 20 - yarn.lock | 2606 ++++++++--------- 112 files changed, 5798 insertions(+), 2624 deletions(-) create mode 100644 css.d.ts create mode 100644 docs/css-variable-inputs.md create mode 100644 docs/known-issues.md create mode 100644 docs/v5-engine-contracts.md create mode 100644 node/__tests__/cache-version.mjs create mode 100644 node/__tests__/component-registry.mjs create mode 100644 node/__tests__/lightningcss-loader.mjs create mode 100644 node/__tests__/tooling.mjs create mode 100644 node/__tests__/typescript.mjs create mode 100644 node/babel.mjs create mode 100644 node/compiler.mjs create mode 100644 node/metro.mjs create mode 100644 src/__fixtures__/types/component-mapping.tsx rename src/__tests__/babel/{plugin.test.mts => plugin.test.ts} (62%) create mode 100644 src/__tests__/babel/runtime-interop.test.ts create mode 100644 src/__tests__/compiler/logical-borders.test.ts create mode 100644 src/__tests__/native/_styled-types.tsx create mode 100644 src/__tests__/native/animation-cancellation-contract.test.tsx create mode 100644 src/__tests__/native/container-boundaries.test.tsx create mode 100644 src/__tests__/native/container-rejection.test.tsx create mode 100644 src/__tests__/native/direction-media.test.tsx create mode 100644 src/__tests__/native/expo57-regressions.test.tsx create mode 100644 src/__tests__/native/filter-list-runtime.test.tsx create mode 100644 src/__tests__/native/gradient-contract.test.tsx create mode 100644 src/__tests__/native/group-attributes.test.tsx create mode 100644 src/__tests__/native/image-background.test.tsx create mode 100644 src/__tests__/native/image-fit.test.tsx create mode 100644 src/__tests__/native/logical-border-runtime.test.tsx create mode 100644 src/__tests__/native/logical-inset-contract.test.tsx create mode 100644 src/__tests__/native/media-query-union.test.tsx create mode 100644 src/__tests__/native/prop-mapping.test.tsx create mode 100644 src/__tests__/native/reactivity-dependencies.test.tsx create mode 100644 src/__tests__/native/reactivity-lifecycle.test.tsx create mode 100644 src/__tests__/native/universal-variables.test.tsx create mode 100644 src/__tests__/native/variable-cycles.test.tsx create mode 100644 src/__tests__/web/mapping.test.tsx create mode 100644 src/metro/cache-version.ts create mode 100644 src/native/styles/scale-factor.ts create mode 100644 src/native/styles/shorthands/logical-border-width.ts create mode 100644 tsconfig.build.json diff --git a/.config/jest.setup.js b/.config/jest.setup.js index 53b5b12d..99790a95 100644 --- a/.config/jest.setup.js +++ b/.config/jest.setup.js @@ -1,3 +1,11 @@ import { setUpTests } from "react-native-reanimated"; +/* global jest */ + +// Worklets 0.10 requires its native implementation to be mocked in Jest. +jest.mock("react-native-worklets", () => + // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- Jest returns the untyped Worklets mock module. + jest.requireActual("react-native-worklets/src/mock"), +); + setUpTests(); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fdb0f9c..3c48dca9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,9 @@ jobs: - name: Build package run: yarn build + - name: Verify Node tooling + run: node --test node/__tests__/*.mjs + - name: Check for unstaged files uses: ./.github/actions/check-unstaged-files diff --git a/css.d.ts b/css.d.ts new file mode 100644 index 00000000..cbe652db --- /dev/null +++ b/css.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/docs/css-variable-inputs.md b/docs/css-variable-inputs.md new file mode 100644 index 00000000..d8680f15 --- /dev/null +++ b/docs/css-variable-inputs.md @@ -0,0 +1,22 @@ +# CSS variable input units + +Use explicit CSS lengths when a variable supplies a dimension in an application that also targets browsers: + +```tsx + + + +``` + +```css +.card { + width: var(--card-width); + opacity: var(--opacity); +} +``` + +The native engine converts pixel strings to React Native numeric dimensions. Fractional values must remain fractional when the variable is inherited or updated. The proposed Expo upgrade fixes the previous truncation of `80.5px` to `80`. + +Browser custom properties retain their CSS token values. A nonzero unitless number is invalid when substituted directly into `width`, while a unitless opacity or scale factor is valid. The library cannot append `px` to every numeric variable without changing those other uses. Use a pixel string for lengths. + +`vars()` remains a deprecated input path; the same unit contract applies. Prefer `VariableContextProvider` for new code. Source tests cover fractional pixel values through both APIs and their updates. Compatibility evidence keeps the original numeric width case distinct from the explicit length case rather than changing the original expectation. diff --git a/docs/known-issues.md b/docs/known-issues.md new file mode 100644 index 00000000..e931d35c --- /dev/null +++ b/docs/known-issues.md @@ -0,0 +1,9 @@ +# Known Expo 57 dependency limitation + +With Expo 57.0.21, React Native 0.86.3, Reanimated 4.5.1, and Worklets 0.10.1, cancelling a CSS animation on Android can leave the component at its last animated transform. A spinning view can remain tilted after switching to `animationName: "none"` or removing the animation styles. In Nativewind, this affects changing `animate-spin` to `animate-none`. + +The failure reproduces with a direct Reanimated `Animated.View` without Nativewind or react-native-css. It is tracked in [Reanimated #10507](https://github.com/software-mansion/react-native-reanimated/issues/10507). The confirmed environment is an Android API 34 emulator with Fabric, Hermes, and a Release build. The integrated physical iPhone cancellation case passed. Later Reanimated versions and physical Android have not been verified. + +The planned RC retains Expo's exact dependency versions and discloses this limitation. Neither library includes the experimental Reanimated patch. Applications relying on CSS animation cancellation must account for this known behavior. No production workaround is currently verified by this release effort. + +The issue includes a [standalone reproduction](https://gist.github.com/danstepanov/03d34ece59f03628deb77a028e8a9a03). When an official fix becomes available in the supported Expo environment, rerun the cancellation and motion checks before removing this notice. This notice does not claim that the RC has been published or that the rest of its release gate is complete. diff --git a/docs/v5-engine-contracts.md b/docs/v5-engine-contracts.md new file mode 100644 index 00000000..6a5652d8 --- /dev/null +++ b/docs/v5-engine-contracts.md @@ -0,0 +1,11 @@ +# V5 engine contract migration notes + +Nativewind v5 dark mode follows the [documented Appearance API](https://www.nativewind.dev/v5/core-concepts/dark-mode). The default `dark:` variant compiles to `prefers-color-scheme: dark`. On native, use React Native `Appearance.setColorScheme("light")` or `Appearance.setColorScheme("dark")` for manual selection, and `useColorScheme` from `react-native` to read it. On this Expo target, `Appearance.setColorScheme("unspecified")` restores the system preference. Web uses its CSS media query; native override verification does not establish a browser override mechanism. + +Legacy `@cssInterop set darkMode ...` configuration and class-qualified `:root` selectors have no native document root contract. The compiler now reports an explicit error before variable optimization can accidentally apply conditional values unconditionally. Migrate theme behavior to media queries and Appearance. Ordinary ancestor class selectors remain supported as selectors; adding a `dark` ancestor is not required by the documented default v5 theme API. + +For preserved CSS variables, use the compiler option `inlineVariables: { exclude: ["--variable-name"] }`. The old `@react-native config { preserve-variables: ... }` directive reports a migration error rather than silently ignoring the option. + +The declared deprecated `nativeStyleToProp` option remains supported as an alias for `nativeStyleMapping`. The current option takes precedence when both are provided, including an empty mapping. `target: false` discards unmapped compiled styles while retaining original inline styles. + +Unit verification covers compiler semantics, Appearance event subscription, mapping destinations and restoration, and animation metadata delivered to Reanimated. It does not prove native frame interpolation or OS event delivery. The new engine package still requires renderer verification before RC approval. diff --git a/example/example-env.d.ts b/example/example-env.d.ts index 473923c5..29390898 100644 --- a/example/example-env.d.ts +++ b/example/example-env.d.ts @@ -1,57 +1 @@ -// This file is should be auto generated, you do not need to use this file - -export * from "react-native"; - -declare module "react-native" { - interface ScrollViewProps - extends ViewProps, - ScrollViewPropsIOS, - ScrollViewPropsAndroid, - Touchable { - contentContainerClassName?: string; - indicatorClassName?: string; - } - interface FlatListProps extends VirtualizedListProps { - columnWrapperClassName?: string; - } - interface ImageBackgroundProps extends ImagePropsBase { - imageClassName?: string; - } - interface ImagePropsBase { - className?: string; - cssInterop?: boolean; - } - interface ViewProps { - className?: string; - cssInterop?: boolean; - } - interface TextInputProps { - placeholderClassName?: string; - } - interface TextProps { - className?: string; - cssInterop?: boolean; - } - interface SwitchProps { - className?: string; - cssInterop?: boolean; - } - interface InputAccessoryViewProps { - className?: string; - cssInterop?: boolean; - } - interface TouchableWithoutFeedbackProps { - className?: string; - cssInterop?: boolean; - } - interface StatusBarProps { - className?: string; - cssInterop?: boolean; - } - interface KeyboardAvoidingViewProps extends ViewProps { - contentContainerClassName?: string; - } - interface ModalBaseProps { - presentationClassName?: string; - } -} +/// diff --git a/example/package.json b/example/package.json index 38b14908..294de3bd 100644 --- a/example/package.json +++ b/example/package.json @@ -11,17 +11,21 @@ "web": "expo start --web" }, "dependencies": { - "@expo/metro-runtime": "~6.1.2", + "@babel/core": "^7.29.0", + "@expo/metro-config": "57.0.12", + "@expo/metro-runtime": "~57.0.15", + "@react-native/metro-config": "0.86.3", "@tailwindcss/postcss": "^4.1.11", - "expo": "54.0.10", - "expo-status-bar": "~3.0.8", - "react": "19.1.0", - "react-dom": "19.1.0", - "react-native": "0.81.4", + "expo": "57.0.22", + "expo-status-bar": "~57.0.1", + "lightningcss": "^1.30.1", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-native": "0.86.3", "react-native-css": "link:../", - "react-native-reanimated": "~4.1.0", - "react-native-web": "~0.21.1", - "react-native-worklets": "~0.5.0" + "react-native-reanimated": "4.5.1", + "react-native-web": "~0.21.0", + "react-native-worklets": "0.10.1" }, "private": true } diff --git a/node/__tests__/cache-version.mjs b/node/__tests__/cache-version.mjs new file mode 100644 index 00000000..f1f0bac7 --- /dev/null +++ b/node/__tests__/cache-version.mjs @@ -0,0 +1,99 @@ +import assert from "node:assert/strict"; +import { + copyFileSync, + mkdirSync, + mkdtempSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { test } from "node:test"; + +const require = createRequire(import.meta.url); +const helper = require.resolve("../../dist/commonjs/metro/cache-version.js"); + +/** @param {import("node:test").TestContext} t */ +function fixture(t) { + const root = mkdtempSync(join(tmpdir(), "css-cache-version-")); + t.after(() => { + rmSync(root, { recursive: true, force: true }); + }); + mkdirSync(join(root, "metro")); + mkdirSync(join(root, "compiler")); + copyFileSync(helper, join(root, "metro/cache-version.cjs")); + writeFileSync(join(root, "compiler/value.js"), "exports.value = 14;"); + return { + root, + key: /** @type {{getCacheVersion: (version?: string, options?: object) => string}} */ ( + require(join(root, "metro/cache-version.cjs")) + ).getCacheVersion, + }; +} + +await test("unchanged input and relocated packages have the same fingerprint", (t) => { + const a = fixture(t), + b = fixture(t); + assert.equal(a.key("user", {}), a.key("user", {})); + assert.equal(a.key("user", {}), b.key("user", {})); +}); + +await test("compiler code changes invalidate and restoration recovers the key", (t) => { + const { root, key } = fixture(t); + const before = key("user", {}); + writeFileSync(join(root, "compiler/value.js"), "exports.value = 18;"); + assert.notEqual(key("user", {}), before); + writeFileSync(join(root, "compiler/value.js"), "exports.value = 14;"); + assert.equal(key("user", {}), before); +}); + +await test("new source modules invalidate and removal recovers the key", (t) => { + const { root, key } = fixture(t); + const before = key("user", {}); + writeFileSync(join(root, "compiler/new.js"), "exports.value = 18;"); + assert.notEqual(key("user", {}), before); + rmSync(join(root, "compiler/new.js")); + assert.equal(key("user", {}), before); +}); + +await test("compiler options invalidate the cache", (t) => { + const { key } = fixture(t); + assert.notEqual( + key("user", { inlineRem: 14 }), + key("user", { inlineRem: 18 }), + ); + assert.notEqual(key("user", { inlineVariables: false }), key("user", {})); + assert.notEqual(key("user", { features: { test: true } }), key("user", {})); +}); + +await test("user cacheVersion is preserved and remains an invalidation input", (t) => { + const { key } = fixture(t); + assert.match(key("user", {}), /^user:react-native-css:[0-9a-f]{64}$/); + assert.notEqual(key("user", {}), key("other", {})); + assert.equal(key(undefined, undefined), key("", {})); +}); + +await test("maps, declarations, tests, and unrelated files do not invalidate", (t) => { + const { root, key } = fixture(t); + const before = key("user", {}); + mkdirSync(join(root, "compiler/__tests__")); + for (const name of [ + "compiler/value.js.map", + "compiler/value.d.ts", + "compiler/__tests__/value.js", + "compiler/value.test.js", + "compiler/value.spec.ts", + "README.md", + ]) { + writeFileSync(join(root, name), "ignored"); + } + assert.equal(key("user", {}), before); +}); + +await test("source condition TypeScript modules invalidate", (t) => { + const { root, key } = fixture(t); + const before = key("user", {}); + writeFileSync(join(root, "compiler/source.ts"), "export const value = 18;"); + assert.notEqual(key("user", {}), before); +}); diff --git a/node/__tests__/component-registry.mjs b/node/__tests__/component-registry.mjs new file mode 100644 index 00000000..36aa58a8 --- /dev/null +++ b/node/__tests__/component-registry.mjs @@ -0,0 +1,115 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import process from "node:process"; +import { test } from "node:test"; +import vm from "node:vm"; + +const require = createRequire(import.meta.url); +const root = String( + process.env.CSS_COMPONENT_REGISTRY_ROOT ?? + path.resolve(import.meta.dirname, "../.."), +); +const rnSource = fs.readFileSync(require.resolve("react-native"), "utf8"); +const rnKeys = [...rnSource.matchAll(/^ {2}get (\w+)\(\) \{/gm)].map((match) => + String(match[1]), +); +assert( + rnKeys.length > 80, + "React Native export declarations were not recognized", +); +const wrappers = [ + "ActivityIndicator", + "Button", + "FlatList", + "Image", + "ImageBackground", + "KeyboardAvoidingView", + "Pressable", + "ScrollView", + "Switch", + "Text", + "TextInput", + "TouchableHighlight", + "TouchableOpacity", + "TouchableWithoutFeedback", + "View", + "VirtualizedList", +]; + +/** @param {string} format */ +function registry(format) { + /** @type {string[]} */ + const requests = []; + /** @type {Map>} */ + const components = new Map(); + /** @type {Record} */ + const core = Object.fromEntries( + rnKeys.map((name) => [name, { name, module: "react-native" }]), + ); + for (const name of wrappers) + components.set("./" + name, { [name]: { name, module: "./" + name } }); + /** @type {{exports: Record}} */ + const module = { exports: {} }; + const filename = path.join(root, "dist", format, "components/index.cjs"); + vm.runInNewContext( + fs.readFileSync(filename, "utf8"), + { + module, + /** @param {string} specifier */ + require(specifier) { + requests.push(specifier); + if (specifier === "react-native") return core; + assert( + components.has(specifier), + "Unexpected component module " + specifier, + ); + return components.get(specifier); + }, + }, + { filename }, + ); + return { exports: module.exports, requests, components, core }; +} + +for (const format of ["commonjs", "module"]) { + void test( + format + + " component registry preserves the installed React Native export surface lazily", + () => { + const result = registry(format); + assert.deepEqual(result.requests, []); + const names = Object.getOwnPropertyNames(result.exports); + assert.deepEqual( + rnKeys.filter((name) => !names.includes(name)), + [], + ); + for (const name of rnKeys.filter((name) => !wrappers.includes(name))) { + assert.equal( + result.exports[name], + result.core[name], + "Incorrect core forwarding: " + name, + ); + } + }, + ); + void test( + format + + " component registry uses every styled wrapper, including TouchableWithoutFeedback", + () => { + const result = registry(format); + for (const name of wrappers) { + assert.equal( + result.exports[name], + result.components.get("./" + name)?.[name], + "Incorrect wrapper: " + name, + ); + } + assert.deepEqual( + result.requests, + wrappers.map((name) => "./" + name), + ); + }, + ); +} diff --git a/node/__tests__/lightningcss-loader.mjs b/node/__tests__/lightningcss-loader.mjs new file mode 100644 index 00000000..05eec02c --- /dev/null +++ b/node/__tests__/lightningcss-loader.mjs @@ -0,0 +1,139 @@ +import assert from "node:assert/strict"; +import { + copyFileSync, + mkdirSync, + mkdtempSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { env } from "node:process"; +import { test } from "node:test"; + +const require = createRequire(import.meta.url); +const override = env.CSS_LOADER_UNDER_TEST; +const helper = + typeof override === "string" + ? override + : require.resolve("../../dist/commonjs/compiler/lightningcss-loader.js"); + +/** @param {import("node:test").TestContext} t */ +function fixture(t) { + const root = mkdtempSync(join(tmpdir(), "css-lightning-loader-")); + t.after(() => { + rmSync(root, { recursive: true, force: true }); + }); + /** @param {string} file @param {string} content */ + const put = (file, content) => { + mkdirSync(dirname(join(root, file)), { recursive: true }); + writeFileSync(join(root, file), content); + }; + copyFileSync(helper, join(root, "loader.cjs")); + return { + put, + expo() { + put( + "node_modules/@expo/metro-config/package.json", + JSON.stringify({ name: "@expo/metro-config", version: "57.0.12" }), + ); + }, + /** @param {string} version */ + lightning( + version, + nested = false, + code = 'exports.transform = () => "' + + version + + '"; exports.Features = { selected: "' + + version + + '" };', + ) { + const base = + "node_modules/" + + (nested ? "@expo/metro-config/node_modules/" : "") + + "lightningcss/"; + put( + base + "package.json", + JSON.stringify({ + name: "lightningcss", + version, + exports: { require: "./node/index.js" }, + }), + ); + put(base + "node/index.js", code); + }, + load() { + const loader = + /** @type {{lightningcssLoader: () => {lightningcss: () => string, Features: {selected: string}}}} */ ( + require(join(root, "loader.cjs")) + ); + return loader.lightningcssLoader(); + }, + }; +} + +await test("uses the Expo dependency before the consumer dependency", (t) => { + const f = fixture(t); + f.expo(); + f.lightning("1.30.1", true); + f.lightning("1.30.2"); + const result = f.load(); + assert.equal(result.lightningcss(), "1.30.1"); + assert.deepEqual(result.Features, { selected: "1.30.1" }); +}); +await test("rejects Expo's blocked version even when the consumer has a good version", (t) => { + const f = fixture(t); + f.expo(); + f.lightning("1.30.2", true); + f.lightning("1.30.1"); + assert.throws( + () => f.load(), + /lightningcss version 1\.30\.2 has a critical bug/, + ); +}); +await test("rejects the blocked version in a standalone consumer", (t) => { + const f = fixture(t); + f.lightning("1.30.2"); + assert.throws( + () => f.load(), + /lightningcss version 1\.30\.2 has a critical bug/, + ); +}); +await test("standalone consumer loads a supported version", (t) => { + const f = fixture(t); + f.lightning("1.30.1"); + assert.equal(f.load().lightningcss(), "1.30.1"); +}); +await test("Expo without a private dependency uses the hoisted consumer copy", (t) => { + const f = fixture(t); + f.expo(); + f.lightning("1.30.1"); + assert.equal(f.load().lightningcss(), "1.30.1"); +}); +await test("missing compiler reports the engine installation error", (t) => { + const f = fixture(t); + f.expo(); + assert.throws(() => f.load(), /unable to determine the path to lightningcss/); +}); +await test("native compiler load errors propagate without silently selecting another copy", (t) => { + const f = fixture(t); + f.expo(); + f.lightning("1.30.1", true, 'throw new Error("native binding missing");'); + f.lightning("1.30.1"); + assert.throws(() => f.load(), /native binding missing/); +}); +await test("later versions are not rejected by the exact version guard", (t) => { + const f = fixture(t); + f.lightning("1.30.3"); + assert.equal(f.load().lightningcss(), "1.30.3"); +}); + +await test("missing optional package metadata does not prevent loading", (t) => { + const f = fixture(t); + f.put( + "node_modules/lightningcss.js", + 'exports.transform = () => "custom"; exports.Features = {};', + ); + assert.equal(f.load().lightningcss(), "custom"); +}); diff --git a/node/__tests__/tooling.mjs b/node/__tests__/tooling.mjs new file mode 100644 index 00000000..bd79d564 --- /dev/null +++ b/node/__tests__/tooling.mjs @@ -0,0 +1,53 @@ +import assert from "node:assert/strict"; +import { createRequire } from "node:module"; +import { test } from "node:test"; + +const require = createRequire(import.meta.url); + +await test("Node ESM compiler uses the CommonJS compiler", async () => { + const esm = await import("react-native-css/compiler"); + const commonjs = require("react-native-css/compiler"); + assert.equal(esm.compile, commonjs.compile); + const result = esm.compile(".probe { width: 37px }").stylesheet(); + assert.equal(result.s[0][0], "probe"); + assert.equal(result.s[0][1][0].d[0].width, 37); +}); + +await test("Node ESM Metro accepts object and lazy configurations", async () => { + const esm = await import("react-native-css/metro"); + const commonjs = require("react-native-css/metro"); + assert.equal(esm.withReactNativeCSS, commonjs.withReactNativeCSS); + const config = { resolver: { sourceExts: ["js"] }, marker: 37 }; + const options = { disableTypeScriptGeneration: true }; + const output = esm.withReactNativeCSS(config, options); + assert.equal(output.marker, 37); + assert.deepEqual(output.resolver.sourceExts, ["js", "css"]); + let calls = 0; + const lazy = esm.withReactNativeCSS(() => { + calls++; + return Promise.resolve(config); + }, options); + assert.equal(calls, 0); + assert.equal((await lazy()).marker, 37); + assert.equal(calls, 1); +}); + +await test("Node ESM Babel default remains callable and rewrites imports", async () => { + const esm = await import("react-native-css/babel"); + const commonjs = require("react-native-css/babel"); + assert.equal(esm.default, commonjs.default); + const preset = esm.default(); + assert.equal(typeof preset.plugins[0], "function"); + assert(preset.plugins.includes("react-native-worklets/plugin")); + const babel = require("@babel/core"); + const result = await babel.transformAsync( + 'import { View } from "react-native";', + { + filename: "/tmp/node-tooling-consumer.js", + babelrc: false, + configFile: false, + presets: [esm.default], + }, + ); + assert.match(result.code, /react-native-css\/components/); +}); diff --git a/node/__tests__/typescript.mjs b/node/__tests__/typescript.mjs new file mode 100644 index 00000000..54689660 --- /dev/null +++ b/node/__tests__/typescript.mjs @@ -0,0 +1,173 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import { createRequire } from "node:module"; +import os from "node:os"; +import path from "node:path"; +import process from "node:process"; +import { test } from "node:test"; + +const require = createRequire(import.meta.url); +const ts = require("typescript"); +const { setupTypeScript } = + /** @type {{setupTypeScript: (envPath?: string, name?: string) => void}} */ ( + require( + String( + process.env.CSS_TYPESCRIPT_SETUP_ENTRY ?? + "../../dist/commonjs/metro/typescript.js", + ), + ) + ); + +const scenarios = [ + { + name: "relative inherited files", + config: { extends: "./config/base.json" }, + basePath: "config/base.json", + base: { files: ["../src/app.ts"] }, + }, + { name: "implicit include", config: {} }, + { name: "explicit include", config: { include: ["src"] } }, + { + name: "inherited include", + config: { extends: "./base.json" }, + base: { include: ["src"] }, + }, + { name: "files only", config: { files: ["src/app.ts"] } }, + { + name: "inherited files", + config: { extends: "./base.json" }, + base: { files: ["src/app.ts"] }, + }, + { + name: "excluded environment", + config: { exclude: ["react-native-css-env.d.ts"] }, + }, + { + name: "inherited exclusion", + config: { extends: "./base.json" }, + base: { exclude: ["react-native-css-env.d.ts"] }, + }, + { name: "nested invocation", config: { include: ["src"] }, cwd: "src" }, + { + name: "custom environment", + config: { include: ["src"] }, + env: "custom.d.ts", + }, + { + name: "relative inherited include", + config: { extends: "./config/base.json" }, + basePath: "config/base.json", + base: { include: ["../src"] }, + }, +]; +for (const scenario of scenarios) { + await test(`TypeScript setup includes declarations and preserves project files: ${scenario.name}`, () => { + const directory = fs.realpathSync( + fs.mkdtempSync(path.join(os.tmpdir(), "css-typescript-")), + ); + const previous = process.cwd(); + try { + fs.mkdirSync(directory + "/src"); + fs.writeFileSync(directory + "/src/app.ts", "export const value = 1;"); + fs.writeFileSync(directory + "/outside.ts", "export const outside = 1;"); + fs.writeFileSync( + directory + "/tsconfig.json", + "// Preserve this project comment\n" + JSON.stringify(scenario.config), + ); + if (scenario.base) { + const file = directory + "/" + (scenario.basePath ?? "base.json"); + fs.mkdirSync(path.dirname(file), { recursive: true }); + fs.writeFileSync(file, JSON.stringify(scenario.base)); + } + const parsed = () => + ts.getParsedCommandLineOfConfigFile( + directory + "/tsconfig.json", + {}, + { + ...ts.sys, + onUnRecoverableConfigFileDiagnostic: (d) => { + throw new Error( + ts.flattenDiagnosticMessageText(d.messageText, "\n"), + ); + }, + }, + ); + const before = parsed().fileNames; + process.chdir(directory + (scenario.cwd ? "/" + scenario.cwd : "")); + const env = scenario.env ?? "react-native-css-env.d.ts"; + setupTypeScript(scenario.env); + const envFile = path.resolve(env); + assert(fs.existsSync(envFile)); + const after = parsed().fileNames; + assert( + after.includes(envFile), + "Generated declaration is absent from the TypeScript project", + ); + assert.deepEqual( + after.filter((f) => f !== envFile).sort(), + before.sort(), + "Existing project membership changed", + ); + const configBytes = fs.readFileSync(directory + "/tsconfig.json", "utf8"); + const envBytes = fs.readFileSync(envFile, "utf8"); + assert(configBytes.includes("// Preserve this project comment")); + setupTypeScript(scenario.env); + assert.equal( + fs.readFileSync(directory + "/tsconfig.json", "utf8"), + configBytes, + ); + assert.equal(fs.readFileSync(envFile, "utf8"), envBytes); + } finally { + process.chdir(previous); + fs.rmSync(directory, { recursive: true, force: true }); + } + }); +} + +await test("TypeScript setup preserves a user owned environment file", () => { + const directory = fs.realpathSync( + fs.mkdtempSync(path.join(os.tmpdir(), "css-typescript-")), + ); + const previous = process.cwd(); + try { + process.chdir(directory); + fs.writeFileSync("tsconfig.json", '{"include":[]}'); + fs.writeFileSync("custom.d.ts", "// existing custom declaration\n"); + setupTypeScript("custom.d.ts"); + assert.equal( + fs.readFileSync("custom.d.ts", "utf8"), + "// existing custom declaration\n", + ); + } finally { + process.chdir(previous); + fs.rmSync(directory, { recursive: true, force: true }); + } +}); + +for (const content of [ + null, + "invalid config", + "[]", + "null", + "42", + "true", + '"hello"', +]) { + await test(`TypeScript setup leaves unavailable or invalid configs alone: ${JSON.stringify(content)}`, () => { + const directory = fs.realpathSync( + fs.mkdtempSync(path.join(os.tmpdir(), "css-typescript-")), + ); + const previous = process.cwd(); + try { + process.chdir(directory); + if (content !== null) fs.writeFileSync("tsconfig.json", content); + setupTypeScript(); + assert(!fs.existsSync("react-native-css-env.d.ts")); + if (content !== null) + assert.equal(fs.readFileSync("tsconfig.json", "utf8"), content); + } finally { + process.chdir(previous); + fs.rmSync(directory, { recursive: true, force: true }); + } + }); +} diff --git a/node/babel.mjs b/node/babel.mjs new file mode 100644 index 00000000..0d716848 --- /dev/null +++ b/node/babel.mjs @@ -0,0 +1,4 @@ +// Preserve the callable default export when Node loads the preset as ESM. +import implementation from "../dist/commonjs/babel/index.js"; + +export default implementation.default; diff --git a/node/compiler.mjs b/node/compiler.mjs new file mode 100644 index 00000000..c87974dc --- /dev/null +++ b/node/compiler.mjs @@ -0,0 +1,2 @@ +// Node ESM entry for the CommonJS tooling implementation. +export { compile } from "../dist/commonjs/compiler/index.js"; diff --git a/node/metro.mjs b/node/metro.mjs new file mode 100644 index 00000000..b50b5ad3 --- /dev/null +++ b/node/metro.mjs @@ -0,0 +1,2 @@ +// Node ESM entry for the CommonJS tooling implementation. +export { withReactNativeCSS } from "../dist/commonjs/metro/index.js"; diff --git a/package.json b/package.json index 29c04232..6b612a45 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "source": "./src/compiler/index.ts", "import": { "types": "./dist/typescript/module/src/compiler/index.d.ts", - "default": "./dist/module/compiler/index.js" + "default": "./node/compiler.mjs" }, "require": { "types": "./dist/typescript/commonjs/src/compiler/index.d.ts", @@ -29,7 +29,7 @@ "source": "./src/babel/index.ts", "import": { "types": "./dist/typescript/module/src/babel/index.d.ts", - "default": "./dist/module/babel/index.js" + "default": "./node/babel.mjs" }, "require": { "types": "./dist/typescript/commonjs/src/babel/index.d.ts", @@ -59,6 +59,30 @@ "default": "./dist/commonjs/components/react-native-safe-area-context.js" } }, + "./components/copyComponentProperties": { + "source": "./src/components/copyComponentProperties.ts", + "react-native": "./src/components/copyComponentProperties.ts", + "import": { + "types": "./dist/typescript/module/src/components/copyComponentProperties.d.ts", + "default": "./dist/module/components/copyComponentProperties.js" + }, + "require": { + "types": "./dist/typescript/commonjs/src/components/copyComponentProperties.d.ts", + "default": "./dist/commonjs/components/copyComponentProperties.js" + } + }, + "./components/index": { + "source": "./src/components/index.cts", + "import": { + "default": "./dist/module/components/index.cjs", + "types": "./dist/typescript/module/src/components/index.d.ts" + }, + "require": { + "default": "./dist/commonjs/components/index.cjs", + "types": "./dist/typescript/commonjs/src/components/index.d.ts" + }, + "react-native": "./src/components/index.cts" + }, "./components/*": { "source": "./src/components/*.tsx", "react-native": "./src/components/*.tsx", @@ -87,7 +111,7 @@ "source": "./src/metro/index.ts", "import": { "types": "./dist/typescript/module/src/metro/index.d.ts", - "default": "./dist/module/metro/index.js" + "default": "./node/metro.mjs" }, "require": { "types": "./dist/typescript/commonjs/src/metro/index.d.ts", @@ -164,7 +188,7 @@ "start": "yarn example start", "start:build": "yarn build && yarn example build", "start:debug": "yarn build && yarn example debug", - "test": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --experimental-vm-modules\" jest", + "test": "jest", "typecheck": "tsc --noEmit" }, "keywords": [ @@ -191,10 +215,13 @@ "files": [ "src", "dist", + "node", "types.d.ts", "!**/__tests__", "!**/__fixtures__", - "!**/__mocks__" + "!**/__mocks__", + "!**/*.test.*", + "!**/*.spec.*" ], "dependencies": { "@types/debug": "^4.1.12", @@ -210,46 +237,48 @@ "react-native": ">=0.81" }, "devDependencies": { - "@babel/core": "^7.28.0", + "@babel/core": "^7.29.0", "@commitlint/config-conventional": "^20.5.0", "@eslint/js": "^10.0.1", - "@expo/metro-config": "~54.0.5", + "@expo/metro-config": "57.0.12", "@ianvs/prettier-plugin-sort-imports": "^4.4.2", + "@react-native/jest-preset": "0.86.3", + "@react-native/metro-config": "0.86.3", "@release-it/conventional-changelog": "10.0.1", "@tailwindcss/postcss": "^4.1.12", "@testing-library/react-native": "^13.3.3", "@tsconfig/react-native": "^3.0.6", "@types/babel__core": "^7", "@types/jest": "^30.0.0", - "@types/react": "^19.1.10", + "@types/react": "~19.2.0", "@types/react-test-renderer": "^19", "babel-plugin-tester": "^12.0.0", - "babel-preset-expo": "~54.0.3", + "babel-preset-expo": "57.0.11", "commitlint": "^20.0.0", "eas-build-cache-provider": "^16.4.2", "eslint": "^9.30.1", "eslint-config-prettier": "^10.1.5", "eslint-plugin-prettier": "^5.5.1", - "expo": "54.0.10", + "expo": "57.0.22", "jest": "^29.7.0", - "jest-expo": "~56.0.5", + "jest-expo": "57.0.5", "lefthook": "^2.1.5", "lightningcss": "^1.30.1", "metro-runtime": "^0.84.2", "postcss": "^8.5.6", "prettier": "^3.6.2", - "react": "19.1.0", - "react-native": "0.81.4", + "react": "19.2.3", + "react-native": "0.86.3", "react-native-builder-bob": "^0.43.0", - "react-native-reanimated": "~4.1.0", - "react-native-safe-area-context": "5.6.1", - "react-native-worklets": "~0.5.0", + "react-native-reanimated": "4.5.1", + "react-native-safe-area-context": "~5.7.0", + "react-native-worklets": "0.10.1", "react-refresh": "^0.17.0", - "react-test-renderer": "19.1.0", + "react-test-renderer": "19.2.3", "release-it": "^20.2.1", "tailwindcss": "^4.1.12", "tailwindcss-safe-area": "^1.1.0", - "typescript": "^5.9.2", + "typescript": "~6.0.3", "typescript-eslint": "^8.40.0" }, "react-native-builder-bob": { @@ -268,7 +297,12 @@ "esm": true } ], - "typescript" + [ + "typescript", + { + "project": "tsconfig.build.json" + } + ] ] }, "packageManager": "yarn@4.9.2", diff --git a/src/__fixtures__/types/component-mapping.tsx b/src/__fixtures__/types/component-mapping.tsx new file mode 100644 index 00000000..d18a1213 --- /dev/null +++ b/src/__fixtures__/types/component-mapping.tsx @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-deprecated -- Verify the supported vars migration input. */ +import { FlatList, Pressable, ScrollView } from "react-native"; + +import { vars as publicVars } from "react-native-css"; +import { View } from "react-native-css/components"; +import { styled, vars } from "react-native-css/native"; + +// Type checks cover recursive React Native component props without widening paths. +styled(FlatList, { className: "style" }); +styled(Pressable, { className: "style" }); +styled(ScrollView, { + contentClassName: "contentContainerStyle", +}); + +// @ts-expect-error The mapping must still reject an unknown destination. +styled(ScrollView, { className: "missingStyle" }); + +export const variableStyle = ; +export const publicVariableStyle = ( + +); diff --git a/src/__tests__/babel/plugin.test.mts b/src/__tests__/babel/plugin.test.ts similarity index 62% rename from src/__tests__/babel/plugin.test.mts rename to src/__tests__/babel/plugin.test.ts index 04144446..18d3acaa 100644 --- a/src/__tests__/babel/plugin.test.mts +++ b/src/__tests__/babel/plugin.test.ts @@ -11,35 +11,38 @@ pluginTester({ }, tests: { "rewrite imports from within React Native": { - only: true, code: `import View from '../View/View';`, - output: `import { View } from "react-native-css/dist/module/components/View";`, + output: `import { View } from "react-native-css/components/View";`, babelOptions: { filename: "node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js", }, }, "rewrite react-native imports": { + babelOptions: { filename: "/consumer/component.js" }, code: `import { View, Text, StyleSheet, Dimensions } from "react-native";`, - output: `import { View } from "react-native-css/dist/module/components/View"; -import { Text } from "react-native-css/dist/module/components/Text"; + output: `import { View } from "react-native-css/components/View"; +import { Text } from "react-native-css/components/Text"; import { StyleSheet } from "react-native"; import { Dimensions } from "react-native";`, }, "rewrite react-native deep imports": { + babelOptions: { filename: "/consumer/component.js" }, code: `import { View } from "react-native/lib/components/View";`, - output: `import { View } from "react-native-css/dist/module/components/View";`, + output: `import { View } from "react-native-css/components/View";`, }, "rewrite react-native-web imports": { + babelOptions: { filename: "/consumer/component.js" }, code: `import { View, Text, StyleSheet, Dimensions } from "react-native-web";`, - output: `import { View } from "react-native-css/dist/module/components/View"; -import { Text } from "react-native-css/dist/module/components/Text"; + output: `import { View } from "react-native-css/components/View"; +import { Text } from "react-native-css/components/Text"; import { StyleSheet } from "react-native-web"; import { Dimensions } from "react-native-web";`, }, "rewrite react-native-web deep imports": { + babelOptions: { filename: "/consumer/component.js" }, code: `import { View } from "react-native-web/lib/components/View";`, - output: `import { View } from "react-native-css/dist/module/components/View";`, + output: `import { View } from "react-native-css/components/View";`, }, }, }); diff --git a/src/__tests__/babel/react-native-web.test.ts b/src/__tests__/babel/react-native-web.test.ts index 5d54a4da..cdd322da 100644 --- a/src/__tests__/babel/react-native-web.test.ts +++ b/src/__tests__/babel/react-native-web.test.ts @@ -1,9 +1,15 @@ import { pluginTester, type TestObject } from "babel-plugin-tester"; +import { format } from "prettier"; + import plugin from "../../babel/import-plugin"; const appendTitles = (tests: TestObject[]) => { - return tests.map((test) => ({ ...test, title: test.code })); + return tests.map((test) => ({ + ...test, + title: test.code, + babelOptions: { filename: "/consumer/component.js", ...test.babelOptions }, + })); }; describe("react-native-web", () => { @@ -14,6 +20,27 @@ describe("react-native-web", () => { plugins: ["@babel/plugin-syntax-jsx"], }, tests: appendTitles([ + { + code: `import typeof View from 'react-native-web';`, + output: `import typeof View from "react-native-web";`, + babelOptions: { parserOpts: { plugins: ["flow"] } }, + formatResult: (code) => format(code, { parser: "babel-flow" }), + }, + { + code: `import { type View, Text } from 'react-native-web';`, + output: `import { type View } from "react-native-web"; +import { Text } from "react-native-css/components/Text";`, + babelOptions: { parserOpts: { plugins: ["typescript"] } }, + }, + { + code: `import type { View } from 'react-native-web';`, + output: `import type { View } from "react-native-web";`, + babelOptions: { parserOpts: { plugins: ["typescript"] } }, + }, + { + code: `import NativeView from 'react-native-web/dist/exports/View';`, + output: `import { View as NativeView } from "react-native-css/components/View";`, + }, /* import tests */ { code: `import 'react-native-web';`, @@ -74,19 +101,27 @@ describe("react-native-web", () => { }, { code: `const _Text = _interopRequireDefault(require('react-native-web/dist/Text'));`, - output: `const { Text: _Text } = require("react-native-css/components/Text");`, + output: `const _Text = _interopRequireDefault( + require("react-native-css/components/Text"), +);`, }, { code: `const _Text = _interopRequireDefault(require('react-native-web/dist/modules/Text'));`, - output: `const { Text: _Text } = require("react-native-css/components/Text");`, + output: `const _Text = _interopRequireDefault( + require("react-native-css/components/Text"), +);`, }, { code: `const _Text = _interopRequireDefault(require('react-native-web/dist/cjs/Text'));`, - output: `const { Text: _Text } = require("react-native-css/components/Text");`, + output: `const _Text = _interopRequireDefault( + require("react-native-css/components/Text"), +);`, }, { code: `const View = _interopRequireDefault(require('../View'));`, - output: `const { View } = require("react-native-css/components/View");`, + output: `const View = _interopRequireDefault( + require("react-native-css/components/View"), +);`, babelOptions: { filename: "react-native-web/dist/modules/ScrollView/index.js", }, diff --git a/src/__tests__/babel/react-native.test.ts b/src/__tests__/babel/react-native.test.ts index a0472936..6ec71b3c 100644 --- a/src/__tests__/babel/react-native.test.ts +++ b/src/__tests__/babel/react-native.test.ts @@ -1,9 +1,15 @@ import { pluginTester, type TestObject } from "babel-plugin-tester"; +import { format } from "prettier"; + import plugin from "../../babel/import-plugin"; const appendTitles = (tests: TestObject[]) => { - return tests.map((test) => ({ ...test, title: test.code })); + return tests.map((test) => ({ + ...test, + title: test.code, + babelOptions: { filename: "/consumer/component.js", ...test.babelOptions }, + })); }; describe("react-native", () => { @@ -15,6 +21,27 @@ describe("react-native", () => { filename: "/someFile.js", }, tests: appendTitles([ + { + code: `import typeof View from 'react-native';`, + output: `import typeof View from "react-native";`, + babelOptions: { parserOpts: { plugins: ["flow"] } }, + formatResult: (code) => format(code, { parser: "babel-flow" }), + }, + { + code: `import { type View, Text } from 'react-native';`, + output: `import { type View } from "react-native"; +import { Text } from "react-native-css/components/Text";`, + babelOptions: { parserOpts: { plugins: ["typescript"] } }, + }, + { + code: `import type { View } from 'react-native';`, + output: `import type { View } from "react-native";`, + babelOptions: { parserOpts: { plugins: ["typescript"] } }, + }, + { + code: `import NativeView from 'react-native/Libraries/Components/View/View';`, + output: `import { View as NativeView } from "react-native-css/components/View";`, + }, { code: `import 'react-native';`, output: `import "react-native-css/components";`, diff --git a/src/__tests__/babel/runtime-interop.test.ts b/src/__tests__/babel/runtime-interop.test.ts new file mode 100644 index 00000000..57d96645 --- /dev/null +++ b/src/__tests__/babel/runtime-interop.test.ts @@ -0,0 +1,118 @@ +import { resolve } from "node:path"; +import { transformSync } from "@babel/core"; + +import plugin from "../../babel/import-plugin"; + +const component = () => null; +function execute( + source: string, + modules: Record, + filename = "/consumer/index.js", +) { + const output = transformSync(source, { + configFile: false, + babelrc: false, + filename, + plugins: [plugin], + })?.code; + expect(output).toBeDefined(); + if (output === undefined || output === null) + throw new Error("Missing transformed output"); + const module = { exports: {} }; + // Execute emitted CommonJS against explicit module identities. + // eslint-disable-next-line @typescript-eslint/no-implied-eval + const run = new Function("require", "module", output) as ( + require: (id: string) => unknown, + module: { exports: unknown }, + ) => void; + run((id: string) => { + if (!(id in modules)) throw new Error(`Unexpected import ${id}`); + return modules[id]; + }, module); + return module.exports; +} + +test.each([ + "react-native-web/dist/Text", + "react-native-web/dist/cjs/Text", + "react-native-web/dist/modules/Text", +])("preserves default interop for %s", (source) => { + expect( + execute( + `function _interopRequireDefault(value) { return value && value.__esModule ? value : { default: value }; } + const Text = _interopRequireDefault(require(${JSON.stringify(source)})); + module.exports = Text.default;`, + { + "react-native-css/components/Text": { + __esModule: true, + default: component, + Text: component, + }, + }, + ), + ).toBe(component); +}); + +test("preserves the default wrapper for a CommonJS component", () => { + expect( + execute( + `function _interopRequireDefault(value) { return value && value.__esModule ? value : { default: value }; } + const Text = _interopRequireDefault(require("react-native-web/dist/Text")); + module.exports = Text.default;`, + { "react-native-css/components/Text": component }, + ), + ).toBe(component); +}); + +test.each(["react-native", "react-native-web"])( + "preserves computed destructuring keys in %s", + (source) => { + expect( + execute( + `const View = "Text"; + const { [View]: Selected } = require(${JSON.stringify(source)}); + module.exports = Selected;`, + { [source]: { Text: component } }, + ), + ).toBe(component); + }, +); + +test.each([ + 'const { Text, ...other } = require("react-native"); module.exports = Text;', + 'const { Text = null } = require("react-native"); module.exports = Text;', + 'const Text = require("react-native").Text; module.exports = Text;', + 'const { Text } = require("react-native"), other = 1; module.exports = Text;', + 'let Text; Text = require("react-native").Text; module.exports = Text;', + 'const name = "react-native"; const { Text } = require(name); module.exports = Text;', +])("preserves unsupported dynamic or combined declaration: %s", (source) => { + expect(execute(source, { "react-native": { Text: component } })).toBe( + component, + ); +}); + +test("does not rewrite the package own source imports", () => { + expect( + execute( + 'const { Text } = require("react-native"); module.exports = Text;', + { "react-native": { Text: component } }, + resolve(__dirname, "../../components/Text.tsx"), + ), + ).toBe(component); +}); + +test.each(["react-native", "react-native-web"])( + "does not rewrite a locally bound require for %s", + (name) => { + expect( + execute( + `function load(require) { + const { Text } = require(${JSON.stringify(name)}); + return Text; + } + module.exports = load(name => ({ Text: name === ${JSON.stringify(name)} ? 37 : 99 }));`, + {}, + ), + ).toBe(37); + }, +); diff --git a/src/__tests__/babel/smoke.test.ts b/src/__tests__/babel/smoke.test.ts index ad5c202c..89df40ac 100644 --- a/src/__tests__/babel/smoke.test.ts +++ b/src/__tests__/babel/smoke.test.ts @@ -3,7 +3,11 @@ import { pluginTester, type TestObject } from "babel-plugin-tester"; import plugin from "../../babel/import-plugin"; const appendTitles = (tests: TestObject[]) => { - return tests.map((test) => ({ ...test, title: test.code })); + return tests.map((test) => ({ + ...test, + title: test.code, + babelOptions: { filename: "/consumer/component.js", ...test.babelOptions }, + })); }; describe("plugin smoke tests", () => { diff --git a/src/__tests__/babel/tsconfig.json b/src/__tests__/babel/tsconfig.json index 2b8f5fbd..dcb0087b 100644 --- a/src/__tests__/babel/tsconfig.json +++ b/src/__tests__/babel/tsconfig.json @@ -1,6 +1,4 @@ { "extends": "../../../tsconfig.json", - "include": [ - "./*" - ] -} \ No newline at end of file + "include": ["./*"] +} diff --git a/src/__tests__/compiler/compiler.test.tsx b/src/__tests__/compiler/compiler.test.tsx index 7a9fbea8..b08e24c3 100644 --- a/src/__tests__/compiler/compiler.test.tsx +++ b/src/__tests__/compiler/compiler.test.tsx @@ -77,71 +77,26 @@ test(":root CSS variables with media queries", () => { }); }); -test.skip("removes unused CSS variables", () => { - const compiled = compile(` - .test { - --blue: blue; - --green: green; - --red: red; - color: var(--red, var(--blue)) - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "test", - [ - [ - { - s: [1, 1], - v: [ - ["blue", "blue"], - ["red", "red"], - ], - dv: 1, - d: [[[{}, "var", ["red", [{}, "var", ["blue"]]]], "color", 1]], - }, - ], - ], - ], - ], - }); +test("removes unused CSS variables while preserving the resolved value", () => { + const result = compile( + `.test { --blue: blue; --green: green; --red: red; color: var(--red, var(--blue)); }`, + ).stylesheet(); + const rule = result.s?.[0]?.[1][0]; + expect(rule?.d).toContainEqual({ color: "#f00" }); + expect( + rule?.v?.filter(([name]) => ["red", "blue", "green"].includes(name)) ?? [], + ).toEqual([]); }); -test.skip("preserves unused CSS variables with preserve-variables", () => { - const compiled = compile(` - @react-native config { - preserve-variables: --green, --blue; - } - - .test { - --green: green; - --red: red; - color: var(--red) - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "test", - [ - [ - { - s: [1, 1], - v: [ - ["green", "green"], - ["red", "red"], - ], - d: [[[{}, "var", ["red"]], "color", 1]], - dv: 1, - }, - ], - ], - ], - ], - }); +test("preserves excluded CSS variables using the public compiler option", () => { + const result = compile( + `.test { --green: green; --red: red; color: var(--red); }`, + { inlineVariables: { exclude: ["--green"] } }, + ).stylesheet(); + const rule = result.s?.[0]?.[1][0]; + expect(rule?.v).toContainEqual(["green", "green"]); + expect(rule?.d).toContainEqual({ color: "#f00" }); + expect(rule?.v?.some(([name]) => name === "red")).toBe(false); }); test("multiple rules with same selector", () => { @@ -187,90 +142,43 @@ test("multiple rules with same selector", () => { }); }); -test.skip("transitions", () => { - const compiled = compile(` - .test { - color: red; - transition: color 1s linear; - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "test", - [ - [ - { - d: [ - { - color: "#ff0000", - transitionDelay: [0], - transitionDuration: [1000], - transitionProperty: ["color"], - transitionTimingFunction: ["linear"], - }, - ], - s: [1, 1], - }, - ], - ], - ], - ], +test("transitions retain duration, property, timing, and the animated rule marker", () => { + const rule = compile( + `.test { color: red; transition: color 1s linear; }`, + ).stylesheet().s?.[0]?.[1][0]; + expect(rule?.a).toBe(true); + expect(rule?.d).toContainEqual({ + color: "#f00", + transitionProperty: ["color"], + transitionDuration: [1000], + transitionDelay: [0], + transitionTimingFunction: "linear", }); }); -test.skip("animations", () => { - const compiled = compile(` - .test { - animation: spin 1s linear infinite; - } - - @keyframes spin { - to { - transform: rotate(360deg); - } - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - k: [ - [ - "spin", - [ - { - 0: { transform: [[{}, "rotate", "0deg"]] }, - 100: { transform: [[{}, "rotate", "360deg"]] }, - }, - ], - ], - ], - s: [ - [ - "test", - [ - [ - { - a: 1, - d: [ - { - animationDelay: [0], - animationDirection: ["normal"], - animationDuration: [1000], - animationFillMode: ["none"], - animationIterationCount: ["infinite"], - animationName: [[{}, "animation", ["spin"], 1]], - animationPlayState: ["running"], - animationTimingFunction: ["linear"], - }, - ], - s: [1, 1], - }, - ], - ], - ], +test("animations preserve named keyframes and their timing contract", () => { + const result = compile( + `.test { animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } }`, + ).stylesheet(); + const rule = result.s?.[0]?.[1][0]; + expect(rule?.a).toBe(true); + expect(rule?.d).toContainEqual( + expect.objectContaining({ + animationDuration: [1000], + animationIterationCount: ["infinite"], + animationTimingFunction: "linear", + }), + ); + expect(rule?.d).toContainEqual([ + [[{}, "animationName", ["spin"], 1]], + "animationName", + ]); + expect(result.k).toEqual([ + [ + "spin", + [["to", [[[{}, "transform", [[{}, "rotate", "360deg"]]], "transform"]]]], ], - }); + ]); }); test("breaks apart comma separated variables", () => { diff --git a/src/__tests__/compiler/logical-borders.test.ts b/src/__tests__/compiler/logical-borders.test.ts new file mode 100644 index 00000000..00a2c709 --- /dev/null +++ b/src/__tests__/compiler/logical-borders.test.ts @@ -0,0 +1,164 @@ +import { compile } from "react-native-css/compiler"; + +const getRule = (css: string) => { + const compiled = compile(`.my-class { ${css} }`); + return { + rule: compiled.stylesheet().s?.find((rule) => rule[0] === "my-class")?.[1], + warnings: compiled.warnings(), + }; +}; + +describe("logical border colors", () => { + test("border-inline-start-color", () => { + expect(getRule("border-inline-start-color: red;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderStartColor: "#f00" }] }, + ]); + }); + + test("border-inline-end-color", () => { + expect(getRule("border-inline-end-color: red;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderEndColor: "#f00" }] }, + ]); + }); + + test("border-inline-color", () => { + expect(getRule("border-inline-color: red;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderStartColor: "#f00", borderEndColor: "#f00" }] }, + ]); + }); + + // nativewind/nativewind#1737: colors that resolve at runtime take the + // unparsed path, which previously skipped the border-inline-* renames + test("border-inline-start-color with var()", () => { + expect( + getRule("border-inline-start-color: hsl(var(--primary));").rule, + ).toStrictEqual([ + { + s: [1, 1], + d: [[[{}, "hsl", [{}, "var", "primary", 1]], "borderStartColor", 1]], + dv: 1, + }, + ]); + }); +}); + +describe("logical border widths", () => { + test("border-inline-start-width", () => { + expect(getRule("border-inline-start-width: 2px;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderStartWidth: 2 }] }, + ]); + }); + + test("border-inline-end-width", () => { + expect(getRule("border-inline-end-width: 2px;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderEndWidth: 2 }] }, + ]); + }); + + test("border-inline-width", () => { + expect(getRule("border-inline-width: 2px;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderStartWidth: 2, borderEndWidth: 2 }] }, + ]); + }); +}); + +describe("logical border shorthands", () => { + test("border-block", () => { + expect(getRule("border-block: 2px solid red;").rule).toStrictEqual([ + { + s: [1, 1], + d: [ + { borderBlockColor: "#f00", borderTopWidth: 2, borderBottomWidth: 2 }, + ], + }, + ]); + }); + + test("border-block-start", () => { + expect(getRule("border-block-start: 2px solid red;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderBlockStartColor: "#f00", borderTopWidth: 2 }] }, + ]); + }); + + test("border-block-end", () => { + expect(getRule("border-block-end: 2px solid red;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderBlockEndColor: "#f00", borderBottomWidth: 2 }] }, + ]); + }); + + test("border-inline-start", () => { + expect(getRule("border-inline-start: 2px solid red;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderStartColor: "#f00", borderStartWidth: 2 }] }, + ]); + }); + + test("border-inline-end", () => { + expect(getRule("border-inline-end: 2px solid red;").rule).toStrictEqual([ + { s: [1, 1], d: [{ borderEndColor: "#f00", borderEndWidth: 2 }] }, + ]); + }); + + test("border-inline", () => { + expect(getRule("border-inline: 2px solid red;").rule).toStrictEqual([ + { + s: [1, 1], + d: [ + { + borderStartColor: "#f00", + borderEndColor: "#f00", + borderStartWidth: 2, + borderEndWidth: 2, + }, + ], + }, + ]); + }); +}); + +describe("logical border styles", () => { + test.each([ + "border-block-style", + "border-block-start-style", + "border-block-end-style", + ])( + "%s solid uses the native default without emitting an unsupported prop", + (property) => { + const { rule, warnings } = getRule(`${property}: solid;`); + expect(rule).toBeUndefined(); + expect(warnings).toEqual({}); + }, + ); + + test("a dashed block style reports the unsupported side styles", () => { + const { rule, warnings } = getRule("border-block-style: dashed;"); + expect(rule).toBeUndefined(); + expect(warnings).toEqual({ + values: { + "border-block-start-style": ["dashed"], + "border-block-end-style": ["dashed"], + }, + }); + }); + // React Native only has a uniform borderStyle. solid matches the native + // default and is dropped silently, anything else drops with a warning + test("border-inline-start-style: solid is dropped without warning", () => { + const { rule, warnings } = getRule("border-inline-start-style: solid;"); + expect(rule).toBeUndefined(); + expect(warnings).toStrictEqual({}); + }); + + test("border-inline-start-style: dashed is dropped with a warning", () => { + const { rule, warnings } = getRule("border-inline-start-style: dashed;"); + expect(rule).toBeUndefined(); + expect(warnings).toStrictEqual({ + values: { "border-inline-start-style": ["dashed"] }, + }); + }); + + test("border-inline shorthand with a dashed style warns", () => { + const { warnings } = getRule("border-inline: 2px dashed red;"); + expect(warnings).toStrictEqual({ + values: { "border-inline-style": ["dashed"] }, + }); + }); +}); diff --git a/src/__tests__/compiler/media-query.test.ts b/src/__tests__/compiler/media-query.test.ts index 760ede29..76994001 100644 --- a/src/__tests__/compiler/media-query.test.ts +++ b/src/__tests__/compiler/media-query.test.ts @@ -1,66 +1,24 @@ import { compile } from "react-native-css/compiler"; -describe.skip("platform media queries", () => { - test("android", () => { - const compiled = compile(` - @media android and (min-width: 500px) { - .my-class { color: red; } - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "my-class", - [ - { - s: [1, 1], - d: [{ color: "#ff0000" }], - m: [ - [ - "&", - [ - ["=", "platform", "android"], - [">=", "width", 500], - ], - ], - ], - }, - ], - ], - ], - }); - }); - - test("ios", () => { - const compiled = compile(` - @media ios and (min-width: 500px) { - .my-class { color: red; } - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ +test.each(["android", "ios"])( + "platform media queries combine %s and width conditions", + (platform) => { + const result = compile( + `@media ${platform} and (min-width: 500px) { .my-class { color: red; } }`, + ).stylesheet(); + const rule = result.s?.[0]?.[1][0]; + expect(rule?.d).toContainEqual({ color: "#f00" }); + expect(rule?.m).toEqual([ + [ + "&", [ - "my-class", - [ - { - s: [1, 1], - d: [{ color: "#ff0000" }], - m: [ - "&", - [ - ["=", "platform", "ios"], - [">=", "width", 500], - ], - ], - }, - ], + ["=", "platform", platform], + [">=", "width", 500], ], ], - }); - }); -}); + ]); + }, +); test("@media (hover: hover)", () => { const compiled = compile(` diff --git a/src/__tests__/native/_styled-types.tsx b/src/__tests__/native/_styled-types.tsx new file mode 100644 index 00000000..3e9e3d0f --- /dev/null +++ b/src/__tests__/native/_styled-types.tsx @@ -0,0 +1,98 @@ +import { + Modal, + ScrollView, + StatusBar, + TextInput, + View, + type StyleProp, + type ViewStyle, +} from "react-native"; + +import { styled as rootStyled } from "react-native-css"; +import { FlatList as CssFlatList } from "react-native-css/components/FlatList"; +import { Image as CssImage } from "react-native-css/components/Image"; +import { ScrollView as CssScrollView } from "react-native-css/components/ScrollView"; +import { Text as CssText } from "react-native-css/components/Text"; +// Prewrapped component declarations must expose only real props and mapped sources. +import { View as CssView } from "react-native-css/components/View"; +import { styled as nativeStyled } from "react-native-css/native"; +import { styled as webStyled } from "react-native-css/web"; + +interface Props { + required: number; + style?: StyleProp; +} + +function Base(_props: Props) { + return null; +} + +const Root = rootStyled(Base); +const Native = nativeStyled(Base); +const Web = webStyled(Base); +const Mapped = rootStyled(Base, { customClassName: "style" }); + +// This module is checked by TypeScript and deliberately excluded from Jest. +export const accepted = [ + , + , + , + , +]; + +export const rejected = [ + // @ts-expect-error The base component's required prop remains required. + , + // @ts-expect-error The base component's required prop remains required on native. + , + // @ts-expect-error The base component's required prop remains required on web. + , + // @ts-expect-error Class names must be strings. + , + // @ts-expect-error Class names must be strings on native. + , + // @ts-expect-error Class names must be strings on web. + , + // @ts-expect-error Explicit mapping adds its declared source only. + , +]; + +// @ts-expect-error An explicit mapping must name a real target property. +rootStyled(Base, { customClassName: "missing" }); + +export const components = [ + , + Hello, + , + , + {item.toFixed()}} + columnWrapperClassName="gap-4" + />, +]; +export const invalidComponents = [ + // @ts-expect-error A wrapped View must reject unknown properties. + , + // @ts-expect-error A wrapped Text must reject unknown properties. + , + // @ts-expect-error A wrapped Image must reject unknown properties. + , + // @ts-expect-error A wrapped ScrollView must reject unknown properties. + , + // @ts-expect-error A wrapped FlatList must reject unknown properties. + null} nonexistent="p-4" />, +]; + +export const removedLegacyProps = [ + // @ts-expect-error v5 has no cssInterop opt out prop. + , + // @ts-expect-error Use placeholder: utilities on className instead. + , + // @ts-expect-error Use the React Native indicatorStyle prop. + , + // @ts-expect-error Use the React Native presentationStyle prop. + , + // @ts-expect-error StatusBar is not an automatically styled component. + , +]; diff --git a/src/__tests__/native/animation-cancellation-contract.test.tsx b/src/__tests__/native/animation-cancellation-contract.test.tsx new file mode 100644 index 00000000..81fe9d27 --- /dev/null +++ b/src/__tests__/native/animation-cancellation-contract.test.tsx @@ -0,0 +1,50 @@ +import { StyleSheet } from "react-native"; + +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; + +// Inspect the style contract before Reanimated consumes its CSS fields. +// Actual cancellation is verified separately in the native consumer. +jest.mock("../../native/reanimated", () => ({ + animatedComponentFamily: (component: unknown) => component, +})); + +const animationName = (): unknown => + ( + StyleSheet.flatten(screen.getByTestId("subject").props.style) as + | { animationName?: unknown } + | undefined + )?.animationName; + +describe.each([undefined, false] as const)( + "animation cancellation with inlineVariables=%s", + (inlineVariables) => { + test.each([ + ["literal shorthand", "animation: none"], + ["literal name", "animation-name: none"], + ["variable shorthand", "--motion: none; animation: var(--motion)"], + ["variable name", "--motion: none; animation-name: var(--motion)"], + ])("preserves the cancellation keyword for %s", (_name, cancellation) => { + registerCSS( + `@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } + .spin { animation: spin 1s linear infinite; } + .stop { ${cancellation}; }`, + { inlineVariables }, + ); + render(); + const running = animationName(); + expect(running).toEqual([ + { + from: { transform: [{ rotate: "0deg" }] }, + to: { transform: [{ rotate: "360deg" }] }, + }, + ]); + screen.rerender(); + const stopped = animationName(); + expect(Array.isArray(stopped) ? stopped : [stopped]).toEqual(["none"]); + screen.rerender(); + expect(animationName()).toEqual(running); + }); + }, +); diff --git a/src/__tests__/native/animations.test.tsx b/src/__tests__/native/animations.test.tsx index 8afa080f..c9565d16 100644 --- a/src/__tests__/native/animations.test.tsx +++ b/src/__tests__/native/animations.test.tsx @@ -1,154 +1,220 @@ +import type { ComponentType } from "react"; +import { StyleSheet } from "react-native"; + import { render, screen } from "@testing-library/react-native"; import { View } from "react-native-css/components/View"; import { registerCSS, testID } from "react-native-css/jest"; -// import { getAnimatedStyle } from "react-native-reanimated"; - -const getAnimatedStyle = (..._args: unknown[]): unknown => { - return; -}; - -jest.useFakeTimers(); - -describe.skip("animations", () => { - test("basic animation", () => { - registerCSS(` - .animation-slide-in { - animation-name: slide-in; - animation-duration: 1s; - } - - @keyframes slide-in { - from { - margin-left: 100%; - } - - to { - margin-left: 0%; - } - } - `); - - render(); - - const element = screen.getByTestId(testID); - expect(getAnimatedStyle(element)).toMatchObject({ - marginLeft: "100%", - }); +// Check the engine output at the Reanimated boundary. Native interpolation is +// covered by the Release renderer suite, not by a fake getAnimatedStyle helper. +jest.mock("../../native/reanimated", () => ({ + animatedComponentFamily: (component: unknown) => component, +})); +const style = () => + StyleSheet.flatten(screen.getByTestId(testID).props.style) as + | Record + | undefined; +const horizontal = { from: { marginLeft: "100%" }, to: { marginLeft: "0%" } }; +const vertical = { from: { marginTop: "0%" }, to: { marginTop: "50%" } }; +const keyframes = `@keyframes slide-in { from { margin-left: 100%; } to { margin-left: 0%; } } + @keyframes slide-down { from { margin-top: 0%; } to { margin-top: 50%; } }`; + +test("basic animation delivers keyframes and duration to Reanimated", () => { + registerCSS( + `${keyframes} .motion { animation-name: slide-in; animation-duration: 1s; }`, + ); + render(); + expect(style()).toMatchObject({ + animationName: [horizontal], + animationDuration: [1000], }); }); -describe.skip("animation", () => { - test("updating animation", () => { - registerCSS(` - .animation-slide-in { - animation-name: slide-in; - animation-duration: 1s; - } - - .animation-slide-down { - animation-name: slide-down; - animation-duration: 1s; - } - - @keyframes slide-in { - from { - margin-left: 100%; - } - - to { - margin-left: 0%; - } - } - - @keyframes slide-down { - from { - margin-top: 0%; - } - - to { - margin-top: 50%; - } - } - `); - - render(); - - expect(getAnimatedStyle(screen.getByTestId(testID))).toMatchObject({ - marginLeft: "100%", - }); - - screen.rerender(); - - expect(getAnimatedStyle(screen.getByTestId(testID))).toMatchObject({ - marginTop: "0%", - }); - - jest.advanceTimersByTime(500); - - expect(getAnimatedStyle(screen.getByTestId(testID))).toMatchObject({ - marginTop: "25%", - }); +test("updating animation replaces keyframes, then removes and restores them", () => { + registerCSS( + `${keyframes} .horizontal { animation: slide-in 1s linear; } .vertical { animation: slide-down 2s linear; }`, + ); + render(); + expect(style()).toMatchObject({ + animationName: [horizontal], + animationDuration: [1000], }); + screen.rerender(); + expect(style()).toMatchObject({ + animationName: [vertical], + animationDuration: [2000], + }); + screen.rerender(); + expect(style()?.animationName).toBeUndefined(); + expect(style()?.animationDuration).toBeUndefined(); + screen.rerender(); + expect(style()).toMatchObject({ + animationName: [horizontal], + animationDuration: [1000], + }); +}); - test("parsable shorthand animation", () => { - registerCSS(` - .animation-slide-in { - animation: slide-in 1s; - } - - @keyframes slide-in { - from { - margin-left: 100%; - } - - to { - margin-left: 0%; - } - } - `); - - render(); +test("parsable shorthand animation preserves duration, timing, and fill", () => { + registerCSS( + `${keyframes} .motion { animation: slide-in 1s linear 250ms 2 alternate both; }`, + ); + render(); + expect(style()).toMatchObject({ + animationName: [horizontal], + animationDuration: [1000], + animationDelay: [250], + animationIterationCount: [2], + animationDirection: ["alternate"], + animationFillMode: ["both"], + animationTimingFunction: "linear", + }); +}); - expect(getAnimatedStyle(screen.getByTestId(testID))).toMatchObject({ - marginLeft: "100%", +test.each([ + [undefined, "var(--animation-name) 1s linear"], + [false, "var(--animation-name) 1s linear"], + [undefined, "1s linear var(--animation-name)"], + [false, "1s linear var(--animation-name)"], +] as const)( + "variable shorthand animation updates with inlineVariables=%s and %s", + (inlineVariables, shorthand) => { + registerCSS( + `${keyframes} .motion { animation: ${shorthand}; } + .horizontal { --animation-name: slide-in; } .vertical { --animation-name: slide-down; }`, + { inlineVariables }, + ); + render(); + expect(style()).toMatchObject({ + animationName: horizontal, + animationDuration: 1000, }); - - jest.advanceTimersByTime(1000); - - expect(getAnimatedStyle(screen.getByTestId(testID))).toMatchObject({ - marginLeft: "0%", + screen.rerender(); + expect(style()).toMatchObject({ + animationName: vertical, + animationDuration: 1000, }); - }); + }, +); + +test("an unresolved animation name is omitted and a later variable restores it", () => { + registerCSS( + `${keyframes} .motion { animation-name: var(--animation-name); animation-duration: 1s; } + .horizontal { --animation-name: slide-in; }`, + { inlineVariables: false }, + ); + render(); + expect(style()?.animationName).toBeUndefined(); + screen.rerender(); + expect(style()).toMatchObject({ animationName: horizontal }); + screen.rerender(); + expect(style()?.animationName).toBeUndefined(); +}); - test("unparsable shorthand animation", () => { - registerCSS(` - .animation-slide-in { - --animation-name: slide-in; - animation: var(--animation-name) 1s; - } +test("animation name variables retain a keyframe fallback and none", () => { + registerCSS( + `${keyframes} .motion { animation-name: var(--animation-name, slide-in); } + .stopped { --animation-name: none; }`, + { inlineVariables: false }, + ); + render(); + expect(style()).toMatchObject({ animationName: horizontal }); + screen.rerender(); + expect(style()?.animationName).toBe("none"); + screen.rerender(); + expect(style()).toMatchObject({ animationName: horizontal }); +}); - @keyframes slide-in { - from { - margin-left: 100%; - } +test("an invalid numeric animation name is omitted without throwing", () => { + registerCSS( + `.motion { animation-name: var(--animation-name); --animation-name: 12px; }`, + { inlineVariables: false }, + ); + render(); + expect(style()?.animationName).toBeUndefined(); +}); - to { - margin-left: 0%; - } - } - `); +test("multiple animation names preserve keyframe order and timing metadata", () => { + registerCSS(`${keyframes} .motion { + animation: slide-in 1s linear 250ms 2 alternate both, + slide-down 2s ease-in 500ms infinite reverse forwards; + }`); + render(); + expect(style()).toMatchObject({ + animationName: [horizontal, vertical], + animationDuration: [1000, 2000], + animationDelay: [250, 500], + animationIterationCount: [2, "infinite"], + animationDirection: ["alternate", "reverse"], + animationFillMode: ["both", "forwards"], + animationTimingFunction: ["linear", "ease-in"], + }); +}); - render(); +test("multiple keyframe selectors retain their distinct percentage positions", () => { + registerCSS(`@keyframes pause { 0%, 50% { opacity: 0; } 100% { opacity: 1; } } + .motion { animation: pause 2s linear; }`); + render(); + expect(style()).toMatchObject({ + animationName: [{ "0%, 50%": { opacity: 0 }, "1": { opacity: 1 } }], + }); +}); - expect(getAnimatedStyle(screen.getByTestId(testID))).toMatchObject({ - marginLeft: "100%", - }); +test("advanced timing resolves through the installed Reanimated CSS API", () => { + registerCSS(`${keyframes} .motion { + animation: slide-in 1s cubic-bezier(0.2, 0.4, 0.6, 0.8); + transition: opacity 2s steps(4, jump-end); + }`); + render(); + // Check the actual installed CSS timing constructors. LightningCSS uses + // float32 coordinates and canonicalizes jump-end to its end alias. + const { cubicBezier, steps } = jest.requireActual< + typeof import("react-native-reanimated") + >("react-native-reanimated"); + expect(style()?.animationTimingFunction).toEqual( + cubicBezier( + Math.fround(0.2), + Math.fround(0.4), + Math.fround(0.6), + Math.fround(0.8), + ), + ); + expect(style()?.transitionTimingFunction).toEqual(steps(4, "end")); +}); - jest.advanceTimersByTime(1000); +test("the Reanimated wrapper is cached by original component identity", () => { + const { animatedComponentFamily } = jest.requireActual< + typeof import("../../native/reanimated") + >("../../native/reanimated"); + const native = + jest.requireActual("react-native"); + const wrapped = animatedComponentFamily(native.View); + expect(wrapped).not.toBe(native.View); + expect(animatedComponentFamily(native.View)).toBe(wrapped); + expect(animatedComponentFamily(native.Text)).not.toBe(wrapped); +}); - expect(getAnimatedStyle(screen.getByTestId(testID))).toMatchObject({ - marginLeft: "0%", - }); - }); +test.each(["View", "Text", "Image", "ScrollView", "FlatList"] as const)( + "an existing Reanimated %s keeps its identity", + (name) => { + const { animatedComponentFamily } = jest.requireActual< + typeof import("../../native/reanimated") + >("../../native/reanimated"); + const Animated = jest.requireActual< + typeof import("react-native-reanimated") + >("react-native-reanimated").default; + const component = Animated[name] as unknown as ComponentType; + expect(animatedComponentFamily(component)).toBe(component); + }, +); + +test("a regular component with the same display name still receives a wrapper", () => { + const { animatedComponentFamily } = jest.requireActual< + typeof import("../../native/reanimated") + >("../../native/reanimated"); + const Animated = jest.requireActual( + "react-native-reanimated", + ).default; + const Regular = () => null; + Regular.displayName = (Animated.View as { displayName?: string }).displayName; + expect(animatedComponentFamily(Regular)).not.toBe(Regular); }); diff --git a/src/__tests__/native/attributes.test.tsx b/src/__tests__/native/attributes.test.tsx index 78e1814c..900dde70 100644 --- a/src/__tests__/native/attributes.test.tsx +++ b/src/__tests__/native/attributes.test.tsx @@ -133,3 +133,150 @@ describe("dataSet attribute selector", () => { }); }); }); + +// Selectors 4 section 6: token boundaries, language matching, and ASCII flags. +test.each([ + [ + "|=", + "en", + ["fr", "en", "en-US", "english", "en"], + [false, true, true, false, true], + ], + [ + "~=", + "active", + [ + "inactive", + "before\tactive\nafter", + "active\rafter", + "active\fafter", + "active\u00a0after", + ], + [false, true, true, true, false], + ], +] as const)( + "attribute %s observes exact boundaries across updates", + (operator, token, values, matches) => { + registerCSS( + `.test { width: 40px; } .test[data-test${operator}"${token}"] { width: 80px; }`, + ); + const tree = (value: string) => ( + + ); + render(tree(values[0])); + for (const [i, value] of values.entries()) { + screen.rerender(tree(value)); + expect(screen.getByTestId(testID).props.style.width).toBe( + matches[i] ? 80 : 40, + ); + } + }, +); + +test.each(["=", "~=", "|=", "^=", "$=", "*="] as const)( + "attribute %s preserves explicit ASCII insensitive matching", + (operator) => { + registerCSS( + `.test { width: 40px; } .test[data-test${operator}"ACTIVE" i] { width: 80px; }`, + ); + const tree = (value: string) => ( + + ); + render(tree("active")); + expect(screen.getByTestId(testID).props.style.width).toBe(80); + screen.rerender(tree("closed")); + expect(screen.getByTestId(testID).props.style.width).toBe(40); + screen.rerender(tree("ACTIVE")); + expect(screen.getByTestId(testID).props.style.width).toBe(80); + }, +); + +test.each(["", " s", " i"] as const)( + "attribute case flag %s has ASCII only semantics", + (flag) => { + registerCSS( + `.test { width: 40px; } .test[data-test="Ä"${flag}] { width: 80px; }`, + ); + render( + , + ); + expect(screen.getByTestId(testID).props.style.width).toBe(40); + }, +); + +test("compound classes require complete tokens in either selector order", () => { + registerCSS( + ".base { width: 40px; height: 20px; } .card.active { width: 80px; } .active.card { height: 60px; }", + ); + const tree = (names: string) => ( + + ); + render(tree("card inactive")); + for (const names of [ + "card inactive", + "postcard active", + "card active", + "card inactive", + "postcard active", + "card active", + ]) { + screen.rerender(tree(names)); + expect(screen.getByTestId(testID).props.style).toMatchObject( + names === "card active" + ? { width: 80, height: 60 } + : { width: 40, height: 20 }, + ); + } +}); + +test.each(["", " s"] as const)( + "attribute flag %s preserves letter case", + (flag) => { + registerCSS( + `.test { width: 40px; } .test[data-test="ACTIVE"${flag}] { width: 80px; }`, + ); + const tree = (value: string) => ( + + ); + render(tree("active")); + expect(screen.getByTestId(testID).props.style.width).toBe(40); + screen.rerender(tree("ACTIVE")); + expect(screen.getByTestId(testID).props.style.width).toBe(80); + }, +); + +test.each(["=", "|=", "~=", "^=", "$=", "*="] as const)( + "empty %s attribute operand follows selector rules", + (operator) => { + registerCSS( + `.test { width: 40px; } .test[data-test${operator}""] { width: 80px; }`, + ); + const tree = (value?: string) => ( + + ); + render(tree()); + expect(screen.getByTestId(testID).props.style.width).toBe(40); + screen.rerender(tree("")); + expect(screen.getByTestId(testID).props.style.width).toBe( + operator === "=" || operator === "|=" ? 80 : 40, + ); + screen.rerender(tree()); + expect(screen.getByTestId(testID).props.style.width).toBe(40); + }, +); diff --git a/src/__tests__/native/calc.test.tsx b/src/__tests__/native/calc.test.tsx index b26cfbad..e31bf886 100644 --- a/src/__tests__/native/calc.test.tsx +++ b/src/__tests__/native/calc.test.tsx @@ -202,3 +202,25 @@ test("infinity", () => { borderRadius: 9007199254740990, }); }); + +test("dynamic clamp prefers the minimum when bounds cross and follows new values", () => { + registerCSS(`.test { width: clamp(var(--minimum), var(--preferred), var(--maximum)); } + .crossed { --minimum: 100px; --preferred: 50px; --maximum: 20px; } + .middle { --minimum: 20px; --preferred: 50px; --maximum: 100px; } + .below { --minimum: 20px; --preferred: 10px; --maximum: 100px; } + .above { --minimum: 20px; --preferred: 150px; --maximum: 100px; }`); + const tree = (state: string) => ( + + ); + render(tree("crossed")); + for (const [state, expected] of [ + ["crossed", 100], + ["middle", 50], + ["below", 20], + ["above", 100], + ["crossed", 100], + ] as const) { + screen.rerender(tree(state)); + expect(screen.getByTestId(testID).props.style.width).toBe(expected); + } +}); diff --git a/src/__tests__/native/color-mix.test.tsx b/src/__tests__/native/color-mix.test.tsx index f581d1f1..c6dcb8a0 100644 --- a/src/__tests__/native/color-mix.test.tsx +++ b/src/__tests__/native/color-mix.test.tsx @@ -66,3 +66,52 @@ test("color-mix() - black with transparent (NaN oklab channels)", () => { backgroundColor: "#00000080", }); }); + +// Independent sRGB channel arithmetic, including normalized weights and original alpha. +test.each([ + ["25%, var(--right)", [63.75, 0, 191.25, 1]], + [", var(--right) 25%", [191.25, 0, 63.75, 1]], + [", var(--right)", [127.5, 0, 127.5, 1]], + ["20%, var(--right) 20%", [127.5, 0, 127.5, 0.4]], + ["80%, var(--right) 80%", [127.5, 0, 127.5, 1]], + [", transparent 75%", [255, 0, 0, 0.25]], + ["25%, transparent 25%", [255, 0, 0, 0.25]], +] as const)( + "dynamic color-mix uses weights rather than replacing alpha: %s", + (tail, expected) => { + registerCSS( + `.test { --left: red; --right: blue; background-color: color-mix(in srgb, var(--left) ${tail}); }`, + { inlineVariables: false }, + ); + render(); + const color = screen.getByTestId(testID).props.style + ?.backgroundColor as string; + expect(color).toMatch(/^rgba\(/); + const channels = color.slice(5, -1).split(",").map(Number); + expected.forEach((value, index) => { + expect(channels[index]).toBeCloseTo(value, 6); + }); + }, +); + +test("mixing an already translucent color preserves its alpha and restores changes", () => { + registerCSS( + `.test { background-color: color-mix(in srgb, var(--left) 50%, transparent); } + .half { --left: #ff000080; } .full { --left: red; }`, + { inlineVariables: false }, + ); + const tree = (state: string) => ( + + ); + render(tree("half")); + for (const [state, expected] of [ + ["half", 128 / 255 / 2], + ["full", 0.5], + ["half", 128 / 255 / 2], + ] as const) { + screen.rerender(tree(state)); + const color = screen.getByTestId(testID).props.style + .backgroundColor as string; + expect(Number(color.slice(5, -1).split(",")[3])).toBeCloseTo(expected, 6); + } +}); diff --git a/src/__tests__/native/components.test.tsx b/src/__tests__/native/components.test.tsx index 3a596038..4ec527a9 100644 --- a/src/__tests__/native/components.test.tsx +++ b/src/__tests__/native/components.test.tsx @@ -1,12 +1,16 @@ import { Button as RNButton, + KeyboardAvoidingView as RNKeyboardAvoidingView, TextInput as RNTextInput, + StyleSheet, + View, type ButtonProps, type TextInputProps, } from "react-native"; import { render } from "@testing-library/react-native"; import { copyComponentProperties } from "react-native-css/components/copyComponentProperties"; +import { KeyboardAvoidingView } from "react-native-css/components/KeyboardAvoidingView"; import { TextInput } from "react-native-css/components/TextInput"; import { registerCSS, testID } from "react-native-css/jest"; import { useCssElement } from "react-native-css/native"; @@ -114,3 +118,36 @@ test("nativeStyleMapping with boolean true on custom component", () => { expect(component.props.textAlign).toBe("right"); expect(component.props.style).not.toHaveProperty("textAlign"); }); + +test("KeyboardAvoidingView maps content classes through replacement, removal, and restoration", () => { + registerCSS( + `.first-content { padding: 12px; } .second-content { padding: 24px; }`, + ); + const contentStyle = Object.freeze({ opacity: 0.5 }); + const result = render( + + + , + ); + for (const [className, padding] of [ + ["first-content", 12], + ["second-content", 24], + [undefined, undefined], + ["first-content", 12], + ] as const) { + result.rerender( + + + , + ); + const inner = result.UNSAFE_getByType(RNKeyboardAvoidingView); + const style = StyleSheet.flatten(inner.props.contentContainerStyle); + expect(style.padding).toBe(padding); + expect(style.opacity).toBe(0.5); + expect(contentStyle).toEqual({ opacity: 0.5 }); + } +}); diff --git a/src/__tests__/native/container-boundaries.test.tsx b/src/__tests__/native/container-boundaries.test.tsx new file mode 100644 index 00000000..2d00e340 --- /dev/null +++ b/src/__tests__/native/container-boundaries.test.tsx @@ -0,0 +1,32 @@ +import { fireEvent, render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components"; +import { registerCSS } from "react-native-css/jest"; + +describe.each(["width", "height"])("container %s", (dimension) => { + test.each([ + [">", [false, false, true]], + [">=", [false, true, true]], + ["<", [true, false, false]], + ["<=", [true, true, false]], + ["=", [false, true, false]], + ] as const)("%s includes the correct boundary", (operator, expected) => { + registerCSS(`.parent { container-name: test; container-type: size; } + .child { opacity: 0.5; } + @container test (${dimension} ${operator} 40px) { .child { opacity: 1; } }`); + render( + + + , + ); + for (const [index, size] of [39, 40, 41].entries()) { + fireEvent(screen.getByTestId("parent"), "layout", { + nativeEvent: { + layout: { x: 0, y: 0, width: 200, height: 300, [dimension]: size }, + }, + }); + expect(screen.getByTestId("child").props.style.opacity).toBe( + expected[index] ? 1 : 0.5, + ); + } + }); +}); diff --git a/src/__tests__/native/container-rejection.test.tsx b/src/__tests__/native/container-rejection.test.tsx new file mode 100644 index 00000000..6308af19 --- /dev/null +++ b/src/__tests__/native/container-rejection.test.tsx @@ -0,0 +1,56 @@ +import { fireEvent, render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; + +// Style queries are unsupported. An unsupported condition must not become an +// unconditional rule or disappear from a conjunction with a supported size test. +test.each([ + "style(--theme: dark)", + "not style(--theme: dark)", + "(width > 100px) and style(--theme: dark)", + "style(--theme: dark) and (width > 100px)", + "style(--theme: dark) or style(--theme: light)", + "not ((width > 100px) and style(--theme: dark))", +])("unsupported container predicate does not apply: %s", (condition) => { + registerCSS(` + .container { container-type: inline-size; } + .child { width: 10px; } + @container ${condition} { .child { width: 37px; } } + `); + render( + + + , + ); + for (const width of [200, 50, 200]) { + fireEvent(screen.getByTestId("parent"), "layout", { + nativeEvent: { layout: { width, height: 200 } }, + }); + expect(screen.getByTestId("child")).toHaveStyle({ width: 10 }); + } +}); + +test("supported disjunction retains its size condition and responds to resizing", () => { + registerCSS(` + .container { container-type: inline-size; } + .child { width: 10px; } + @container (width > 100px) or style(--theme: dark) { + .child { width: 37px; } + } + `); + render( + + + , + ); + for (const [width, expected] of [ + [200, 37], + [50, 10], + [200, 37], + ]) { + fireEvent(screen.getByTestId("parent"), "layout", { + nativeEvent: { layout: { width, height: 200 } }, + }); + expect(screen.getByTestId("child")).toHaveStyle({ width: expected }); + } +}); diff --git a/src/__tests__/native/direction-media.test.tsx b/src/__tests__/native/direction-media.test.tsx new file mode 100644 index 00000000..8ed38ee4 --- /dev/null +++ b/src/__tests__/native/direction-media.test.tsx @@ -0,0 +1,35 @@ +import { I18nManager } from "react-native"; + +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; + +const originalDirection = Object.getOwnPropertyDescriptor(I18nManager, "isRTL"); +afterEach(() => { + if (originalDirection) + Object.defineProperty(I18nManager, "isRTL", originalDirection); +}); + +test.each([false, true])( + "direction conditions are exclusive when isRTL=%s", + (isRTL) => { + Object.defineProperty(I18nManager, "isRTL", { + configurable: true, + value: isRTL, + }); + registerCSS(` + .rtl { width: 10px; } + .ltr { width: 10px; } + @media (dir: rtl) { .rtl { width: 37px; } } + @media (dir: ltr) { .ltr { width: 37px; } } + `); + render( + + + + , + ); + expect(screen.getByTestId("rtl")).toHaveStyle({ width: isRTL ? 37 : 10 }); + expect(screen.getByTestId("ltr")).toHaveStyle({ width: isRTL ? 10 : 37 }); + }, +); diff --git a/src/__tests__/native/expo57-regressions.test.tsx b/src/__tests__/native/expo57-regressions.test.tsx new file mode 100644 index 00000000..e0998931 --- /dev/null +++ b/src/__tests__/native/expo57-regressions.test.tsx @@ -0,0 +1,168 @@ +/* eslint-disable @typescript-eslint/no-deprecated -- Verify the supported vars migration input. */ +import { StyleSheet } from "react-native"; + +import { render, screen } from "@testing-library/react-native"; +import { VariableContextProvider } from "react-native-css"; +import { Text } from "react-native-css/components/Text"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; +import { vars } from "react-native-css/native"; + +test.each([ + ["scale: 150% 50%", [{ scaleX: 1.5 }, { scaleY: 0.5 }]], + ["scale: none", [{ scaleX: 1 }, { scaleY: 1 }]], + ["transform: scaleX(150%) scaleY(50%)", [{ scaleX: 1.5 }, { scaleY: 0.5 }]], + [ + "--x: 150%; --y: 50%; scale: var(--x) var(--y)", + [{ scaleX: 1.5 }, { scaleY: 0.5 }], + ], +])("native scale factors: %s", (declarations, expected) => { + registerCSS(`.subject { ${declarations}; }`); + render(); + expect(screen.getByTestId("subject").props.style.transform).toEqual(expected); +}); + +test.each([ + ["line-height: 31px", 31], + ["font-size: 20px; line-height: 31px", 31], + ["font-size: 20px; line-height: 1.5", 30], +])("native line height: %s", (declarations, expected) => { + registerCSS(`.subject { ${declarations}; }`); + render( + + Text + , + ); + expect(screen.getByTestId("subject").props.style.lineHeight).toBe(expected); +}); + +test("public ARIA conditions update and restore", () => { + registerCSS( + `.subject { width: 40px; } .subject[aria-selected="true"] { width: 80px; }`, + ); + const subject = (selected: boolean) => ( + + ); + render(subject(false)); + expect(screen.getByTestId("subject").props.style.width).toBe(40); + screen.rerender(subject(true)); + expect(screen.getByTestId("subject").props.style.width).toBe(80); + screen.rerender(subject(false)); + expect(screen.getByTestId("subject").props.style.width).toBe(40); +}); + +test("vars-only parents provide, update, and remove inherited overrides", () => { + registerCSS(`.subject { width: var(--width, 30px); }`); + const subject = (width?: number) => ( + + + + + + + ); + render(subject(80)); + expect(screen.getByTestId("subject").props.style.width).toBe(80); + expect(screen.getByTestId("sibling").props.style.width).toBe(40); + screen.rerender(subject(90)); + expect(screen.getByTestId("subject").props.style.width).toBe(90); + screen.rerender(subject()); + expect(screen.getByTestId("subject").props.style.width).toBe(40); +}); + +test.each(["scale: var(--factor)", "transform: scale(var(--factor))"])( + "runtime scale variables remain numeric and uniform: %s", + (declaration) => { + registerCSS(`.subject { ${declaration}; }`, { inlineVariables: false }); + const subject = (factor: string) => ( + + ); + const factors = () => { + const transform = screen.getByTestId("subject").props.style + .transform as Record[]; + for (const entry of transform) + for (const value of Object.values(entry)) + expect(typeof value).toBe("number"); + return ["scaleX", "scaleY"].map((axis) => + transform.reduce( + (value, entry) => value * (entry.scale ?? entry[axis] ?? 1), + 1, + ), + ); + }; + render(subject("150%")); + expect(factors()).toEqual([1.5, 1.5]); + screen.rerender(subject("50%")); + expect(factors()).toEqual([0.5, 0.5]); + screen.rerender(subject("-100%")); + expect(factors()).toEqual([-1, -1]); + }, +); + +test.each(["inline", "class"])( + "own %s variables override ancestors and restore after removal", + (mode) => { + registerCSS( + `.subject { width: var(--width); } .override { --width: 80px; }`, + { inlineVariables: false }, + ); + const subject = (override: boolean, parentWidth = 120) => ( + + + + + + + ); + const width = (id: string): unknown => + StyleSheet.flatten(screen.getByTestId(id).props.style).width; + render(subject(true)); + expect(width("subject")).toBe(80); + expect(width("descendant")).toBe(80); + expect(width("sibling")).toBe(120); + screen.rerender(subject(true, 130)); + expect(width("subject")).toBe(80); + expect(width("descendant")).toBe(80); + screen.rerender(subject(false, 130)); + expect(width("subject")).toBe(130); + expect(width("descendant")).toBe(130); + screen.rerender(subject(true, 130)); + expect(width("subject")).toBe(80); + expect(width("descendant")).toBe(80); + expect(width("sibling")).toBe(130); + }, +); + +test.each(["vars", "provider"])( + "explicit pixel variables preserve fractions through %s updates", + (mode) => { + registerCSS(`.subject { width: var(--width); }`, { + inlineVariables: false, + }); + const subject = (width: string) => { + const child = ; + return mode === "vars" ? ( + {child} + ) : ( + + {child} + + ); + }; + render(subject("80.5px")); + expect(screen.getByTestId("subject").props.style.width).toBe(80.5); + screen.rerender(subject("120.25px")); + expect(screen.getByTestId("subject").props.style.width).toBe(120.25); + screen.rerender(subject(".5px")); + expect(screen.getByTestId("subject").props.style.width).toBe(0.5); + }, +); diff --git a/src/__tests__/native/filter-list-runtime.test.tsx b/src/__tests__/native/filter-list-runtime.test.tsx new file mode 100644 index 00000000..0e7a2840 --- /dev/null +++ b/src/__tests__/native/filter-list-runtime.test.tsx @@ -0,0 +1,74 @@ +import { StyleSheet } from "react-native"; + +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; + +const firstShadow = { + dropShadow: { offsetX: 0, offsetY: 1, standardDeviation: 2, color: "#000" }, +}; +const secondShadow = { + dropShadow: { + offsetX: 0, + offsetY: 2, + standardDeviation: 3, + color: "#123456", + }, +}; +const filter = (): unknown => + StyleSheet.flatten(screen.getByTestId("subject").props.style)?.filter; + +describe.each([undefined, false] as const)( + "filter lists with inlineVariables=%s", + (inlineVariables) => { + test("flattens a variable containing two filters and preserves order", () => { + registerCSS( + `.subject { --shadows: drop-shadow(0 1px 2px #000) drop-shadow(0 2px 3px #123456); filter: brightness(0.5) var(--shadows) contrast(2); }`, + { inlineVariables }, + ); + render(); + expect(filter()).toEqual([ + { brightness: 0.5 }, + firstShadow, + secondShadow, + { contrast: 2 }, + ]); + }); + + test("keeps a single function inside a native filter array", () => { + registerCSS( + `.subject { --effect: drop-shadow(0 1px 2px #000); filter: var(--effect); }`, + { inlineVariables }, + ); + render(); + expect(filter()).toEqual([firstShadow]); + }); + + test("updates and removes a variable filter list", () => { + registerCSS( + `.subject { --effect: drop-shadow(0 1px 2px #000) drop-shadow(0 2px 3px #123456); filter: var(--effect); } .changed { --effect: blur(3px); } .clear { filter: none; }`, + { inlineVariables }, + ); + render(); + expect(filter()).toEqual([firstShadow, secondShadow]); + screen.rerender(); + expect(filter()).toEqual([{ blur: 3 }]); + screen.rerender(); + expect(filter()).toBeUndefined(); + screen.rerender(); + expect(filter()).toEqual([firstShadow, secondShadow]); + }); + }, +); + +test("literal filter functions retain React Native property names and order", () => { + registerCSS( + `.subject { filter: brightness(0.5) hue-rotate(90deg) blur(2px); }`, + ); + render(); + expect(filter()).toEqual([ + { brightness: 0.5 }, + { hueRotate: "90deg" }, + { blur: 2 }, + ]); +}); diff --git a/src/__tests__/native/gradient-contract.test.tsx b/src/__tests__/native/gradient-contract.test.tsx new file mode 100644 index 00000000..6f527ca6 --- /dev/null +++ b/src/__tests__/native/gradient-contract.test.tsx @@ -0,0 +1,57 @@ +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components"; +import { registerCSS } from "react-native-css/jest"; + +// Exercise the native prop processor, which the component mock does not run. +const processBackgroundImage = + // eslint-disable-next-line @typescript-eslint/no-require-imports + require("react-native/Libraries/StyleSheet/processBackgroundImage") + .default as (value: unknown) => unknown; + +test.each([ + "none", + "linear-gradient(to right, red 0%, blue 100%)", + "linear-gradient(45deg, red 10px, blue 40px)", + "linear-gradient(to right, red, blue), linear-gradient(to bottom, black, white)", +])("native background image input: %s", (css) => { + registerCSS(`.subject { background-image: ${css}; }`); + render(); + const value = + screen.getByTestId("subject").props.style.experimental_backgroundImage; + expect(() => processBackgroundImage(value)).not.toThrow(); + expect(processBackgroundImage(value)).toEqual(processBackgroundImage(css)); +}); + +test("removing a gradient produces the native empty image value", () => { + registerCSS( + `.on { background-image: linear-gradient(red, blue); } .off { background-image: none; }`, + ); + render(); + expect( + processBackgroundImage( + screen.getByTestId("subject").props.style.experimental_backgroundImage, + ), + ).toHaveLength(1); + screen.rerender(); + expect( + processBackgroundImage( + screen.getByTestId("subject").props.style.experimental_backgroundImage, + ), + ).toEqual([]); +}); + +test.each(["90deg", "to right"])( + "native gradient processor cannot represent explicit oklab interpolation: %s", + (direction) => { + const css = `linear-gradient(${direction} in oklab, red, blue)`; + registerCSS(`.subject { background-image: ${css}; }`); + render(); + const value = + screen.getByTestId("subject").props.style.experimental_backgroundImage; + expect(value).toContain("in oklab"); + expect(processBackgroundImage(value)).toEqual([]); + expect( + processBackgroundImage(`linear-gradient(${direction}, red, blue)`), + ).toHaveLength(1); + }, +); diff --git a/src/__tests__/native/group-attributes.test.tsx b/src/__tests__/native/group-attributes.test.tsx new file mode 100644 index 00000000..1bfeb714 --- /dev/null +++ b/src/__tests__/native/group-attributes.test.tsx @@ -0,0 +1,76 @@ +import { memo } from "react"; + +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components"; +import { registerCSS } from "react-native-css/jest"; + +const Child = memo(() => ); + +test("named attribute groups update memoized descendants and isolate siblings", () => { + registerCSS(` + .group { container-name: group; } + .child { width: 40px; } + .group[data-state="open"] .child { width: 80px; } + `); + const subject = (state?: string) => ( + <> + + + + + + + + ); + render(subject("closed")); + expect(screen.getByTestId("child").props.style.width).toBe(40); + screen.rerender(subject("open")); + expect(screen.getByTestId("child").props.style.width).toBe(80); + expect(screen.getByTestId("sibling").props.style.width).toBe(40); + screen.rerender(subject()); + expect(screen.getByTestId("child").props.style.width).toBe(40); +}); + +test("the nearest named ancestor supplies the attribute condition", () => { + registerCSS(`.group { container-name: group; } .child { width: 40px; } + .group[aria-selected="true"] .child { width: 80px; }`); + const subject = (inner: boolean) => ( + + + + + + ); + render(subject(false)); + expect(screen.getByTestId("child").props.style.width).toBe(40); + screen.rerender(subject(true)); + expect(screen.getByTestId("child").props.style.width).toBe(80); + screen.rerender(subject(false)); + expect(screen.getByTestId("child").props.style.width).toBe(40); +}); + +test("ancestor attribute flags survive is and where selector compilation", () => { + registerCSS(`.child { width: 40px; height: 20px; } + .child:is(.group[data-state="OPEN" i] *) { width: 80px; } + .child:where(.group[data-state="OPEN" i] *) { height: 60px; }`); + const subject = (state: string) => ( + + + + ); + render(subject("closed")); + expect(screen.getByTestId("child").props.style).toMatchObject({ + width: 40, + height: 20, + }); + screen.rerender(subject("open")); + expect(screen.getByTestId("child").props.style).toMatchObject({ + width: 80, + height: 60, + }); + screen.rerender(subject("closed")); + expect(screen.getByTestId("child").props.style).toMatchObject({ + width: 40, + height: 20, + }); +}); diff --git a/src/__tests__/native/grouping.test.tsx b/src/__tests__/native/grouping.test.tsx index 68f1056e..1484e4b8 100644 --- a/src/__tests__/native/grouping.test.tsx +++ b/src/__tests__/native/grouping.test.tsx @@ -2,7 +2,10 @@ import { fireEvent, render, screen } from "@testing-library/react-native"; import { View } from "react-native-css/components/View"; import { registerCSS } from "react-native-css/jest"; -// import { getAnimatedStyle } from "react-native-reanimated"; +// Verify group state at the Reanimated boundary; native motion is separate. +jest.mock("../../native/reanimated", () => ({ + animatedComponentFamily: (component: unknown) => component, +})); const parentID = "parent"; const childID = "child"; @@ -58,43 +61,26 @@ test("group - active", () => { expect(child.props.style).toStrictEqual({ backgroundColor: "#f00" }); }); -test.skip("group - active (animated)", () => { - registerCSS(` - .group\\/item:active .my-class { - color: red; - transition: color 1s; - }`); - +test("group - active (animated) supplies changed and restored transition targets", () => { + registerCSS(`.my-class { color: black; transition: color 1s linear; } + .group:active .my-class { color: red; }`); render( - + , ); - - const parent = screen.getByTestId(parentID); - const child = screen.getByTestId(childID); - - expect(child.props.style).toStrictEqual(undefined); - - fireEvent(parent, "pressIn"); - - jest.advanceTimersByTime(0); - - // expect(getAnimatedStyle(child)).toStrictEqual({ - // color: "rgba(0, 0, 0, 1)", - // }); - - jest.advanceTimersByTime(500); - - // expect(getAnimatedStyle(child)).toStrictEqual({ - // color: "rgba(151, 0, 0, 1)", - // }); - - jest.advanceTimersByTime(500); - - // expect(getAnimatedStyle(child)).toStrictEqual({ - // color: "rgba(255, 0, 0, 1)", - // }); + const expectStyle = (color: string) => { + expect(screen.getByTestId(childID).props.style).toMatchObject({ + color, + transitionProperty: ["color"], + transitionDuration: [1000], + }); + }; + expectStyle("#000"); + fireEvent(screen.getByTestId(parentID), "pressIn"); + expectStyle("#f00"); + fireEvent(screen.getByTestId(parentID), "pressOut"); + expectStyle("#000"); }); test("group selector", () => { diff --git a/src/__tests__/native/image-background.test.tsx b/src/__tests__/native/image-background.test.tsx new file mode 100644 index 00000000..08acacab --- /dev/null +++ b/src/__tests__/native/image-background.test.tsx @@ -0,0 +1,26 @@ +import { ImageBackground as RNImageBackground } from "react-native"; + +import { render, screen } from "@testing-library/react-native"; +import { ImageBackground } from "react-native-css/components/ImageBackground"; +import { registerCSS } from "react-native-css/jest"; + +test("ImageBackground maps image and container classes separately", () => { + registerCSS(`.container { background-color: red; width: 80px; } + .image { opacity: 0.5; } .updated { opacity: 0.75; }`); + const subject = (imageClassName?: string) => ( + + ); + render(subject("image")); + const props = () => screen.UNSAFE_getByType(RNImageBackground).props; + expect(props().style).toEqual({ backgroundColor: "#f00", width: 80 }); + expect(props().imageStyle).toEqual({ opacity: 0.5 }); + expect(props().backgroundColor).toBeUndefined(); + screen.rerender(subject("updated")); + expect(props().imageStyle).toEqual({ opacity: 0.75 }); + screen.rerender(subject()); + expect(props().imageStyle).toBeUndefined(); +}); diff --git a/src/__tests__/native/image-fit.test.tsx b/src/__tests__/native/image-fit.test.tsx new file mode 100644 index 00000000..de49f163 --- /dev/null +++ b/src/__tests__/native/image-fit.test.tsx @@ -0,0 +1,87 @@ +import { + Image as RNImage, + StyleSheet, + View, + type ImageProps, + type ImageStyle, +} from "react-native"; + +import { render, screen } from "@testing-library/react-native"; +import { Image } from "react-native-css/components/Image"; +import { registerCSS } from "react-native-css/jest"; +import { styled } from "react-native-css/native"; + +const props = () => + screen.UNSAFE_getByType(RNImage).props as ImageProps & { + contentFit?: unknown; + }; +const style = (): ImageStyle | undefined => StyleSheet.flatten(props().style); + +test("React Native Image receives CSS object-fit as an image style", () => { + registerCSS(`.contain { object-fit: contain; } .fill { object-fit: fill; }`); + render( + , + ); + expect(style()?.objectFit).toBe("contain"); + expect(props().contentFit).toBeUndefined(); + screen.rerender( + , + ); + expect(style()?.objectFit).toBe("fill"); + screen.rerender(); + expect(style()?.objectFit).toBeUndefined(); +}); + +test("an inline image fit overrides the utility and returns when removed", () => { + registerCSS(`.contain { object-fit: contain; }`); + render( + , + ); + expect(style()?.objectFit).toBe("cover"); + screen.rerender(); + expect(style()?.objectFit).toBe("contain"); +}); + +test("the shared mapping still delivers contentFit to custom image components", () => { + registerCSS(`.contain { object-fit: contain; }`); + const Custom = styled((props: { contentFit?: string }) => ( + + )); + render(); + expect(screen.getByTestId("custom").props.contentFit).toBe("contain"); +}); + +test("an image with a transition can use the animated component adapter", () => { + registerCSS( + `.image { object-fit: contain; opacity: 1; transition: opacity 100ms; } .changed { opacity: 0.5; }`, + ); + render(); + expect(style()?.objectFit).toBe("contain"); + screen.rerender( + , + ); + expect(style()?.objectFit).toBe("contain"); +}); + +test("important image fitting overrides inline style and preserves its siblings", () => { + registerCSS(`.contain { object-fit: contain !important; }`); + render( + , + ); + expect(style()).toMatchObject({ objectFit: "contain", opacity: 0.5 }); + screen.rerender( + , + ); + expect(style()).toMatchObject({ objectFit: "cover", opacity: 0.5 }); +}); diff --git a/src/__tests__/native/logical-border-runtime.test.tsx b/src/__tests__/native/logical-border-runtime.test.tsx new file mode 100644 index 00000000..d0331416 --- /dev/null +++ b/src/__tests__/native/logical-border-runtime.test.tsx @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-deprecated -- Exercise the existing vars input contract. */ +import { StyleSheet } from "react-native"; + +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; +import { vars } from "react-native-css/native"; + +describe.each(["ltr", "rtl"] as const)("logical borders in %s", (direction) => { + test.each([ + ["border-block-width: 2px", { borderTopWidth: 2, borderBottomWidth: 2 }], + [ + "border-block-width: 2px 3px", + { borderTopWidth: 2, borderBottomWidth: 3 }, + ], + ["border-block-start-width: 2px", { borderTopWidth: 2 }], + ["border-block-end-width: 3px", { borderBottomWidth: 3 }], + ])("maps %s to native width props", (declarations, expected) => { + registerCSS(`.subject { ${declarations}; }`); + render(); + expect( + StyleSheet.flatten(screen.getByTestId("subject").props.style), + ).toEqual({ ...expected, direction }); + }); + + test.each([ + ["block", "borderTopWidth", "borderBottomWidth"], + ["inline", "borderStartWidth", "borderEndWidth"], + ])("updates dynamic %s widths", (axis, start, end) => { + registerCSS(`.subject { border-${axis}-width: var(--start) var(--end); }`, { + inlineVariables: false, + }); + const subject = (a: number, b: number) => ( + + + + ); + render(subject(2.5, 3.5)); + expect( + StyleSheet.flatten(screen.getByTestId("subject").props.style), + ).toEqual({ [start]: 2.5, [end]: 3.5, direction }); + screen.rerender(subject(4, 1)); + expect( + StyleSheet.flatten(screen.getByTestId("subject").props.style), + ).toEqual({ [start]: 4, [end]: 1, direction }); + }); + + test("duplicates a single runtime block width onto both sides", () => { + registerCSS(`.subject { border-block-width: var(--width); }`, { + inlineVariables: false, + }); + render( + , + ); + expect( + StyleSheet.flatten(screen.getByTestId("subject").props.style), + ).toEqual({ borderTopWidth: 2.5, borderBottomWidth: 2.5 }); + }); +}); diff --git a/src/__tests__/native/logical-inset-contract.test.tsx b/src/__tests__/native/logical-inset-contract.test.tsx new file mode 100644 index 00000000..5c73da37 --- /dev/null +++ b/src/__tests__/native/logical-inset-contract.test.tsx @@ -0,0 +1,19 @@ +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; + +// Expected native properties are specified independently of the compiler map. +test.each([ + ["inset-inline", "12px", { insetInline: 12 }], + ["inset-inline", "12px 24px", { insetInlineStart: 12, insetInlineEnd: 24 }], + ["inset-inline", "-8px 25%", { insetInlineStart: -8, insetInlineEnd: "25%" }], + ["inset-block", "12px 24px", { insetBlockStart: 12, insetBlockEnd: 24 }], +])("%s: %s preserves its logical axis", (property, value, expected) => { + registerCSS(`.position { ${property}: ${value}; }`); + render(); + expect(screen.getByTestId("position").props.style).toEqual(expected); + screen.rerender(); + expect(screen.getByTestId("position").props.style).toBeUndefined(); + screen.rerender(); + expect(screen.getByTestId("position").props.style).toEqual(expected); +}); diff --git a/src/__tests__/native/media-query-union.test.tsx b/src/__tests__/native/media-query-union.test.tsx new file mode 100644 index 00000000..d8fc14e6 --- /dev/null +++ b/src/__tests__/native/media-query-union.test.tsx @@ -0,0 +1,59 @@ +import { act, render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; + +import { dimensions } from "../../native/reactivity"; + +jest.mock("react-native", () => { + const RN = jest.requireActual("react-native"); + RN.Platform.OS = "ios"; + return RN as unknown; +}); + +test.each([ + ["(min-width: 500px), (max-width: 100px)", [37, 10, 37, 37]], + ["screen, (min-width: 500px)", [37, 37, 37, 37]], + ["not android", [37, 37, 37, 37]], + ["not ios", [10, 10, 10, 10]], + ["not all", [10, 10, 10, 10]], + ["print", [10, 10, 10, 10]], + ["not print and (min-width: 500px)", [37, 37, 37, 37]], + ["(aspect-ratio: 100/1)", [10, 10, 10, 10]], + ["(min-width: 100px) and (aspect-ratio: 100/1)", [10, 10, 10, 10]], +])("media query preserves conditional semantics: %s", (condition, expected) => { + registerCSS(` + .child { width: 10px; } + @media ${condition} { .child { width: 37px; } } + `); + render(); + for (const [index, width] of [50, 200, 700, 50].entries()) { + act(() => { + dimensions.set({ ...dimensions.get(), width, height: 200 }); + }); + expect(screen.getByTestId("child")).toHaveStyle({ width: expected[index] }); + } +}); + +test("nested alternatives retain outer media constraints and variable updates", () => { + registerCSS(` + :root { --size: 10px; } + @media (min-width: 200px) { + @media (max-width: 100px), (min-width: 500px) { + :root { --size: 37px; } + } + } + .child { width: var(--size); } + `); + render(); + for (const [width, expected] of [ + [50, 10], + [200, 10], + [700, 37], + [50, 10], + ] as const) { + act(() => { + dimensions.set({ ...dimensions.get(), width, height: 200 }); + }); + expect(screen.getByTestId("child")).toHaveStyle({ width: expected }); + } +}); diff --git a/src/__tests__/native/prop-mapping.test.tsx b/src/__tests__/native/prop-mapping.test.tsx new file mode 100644 index 00000000..f558419b --- /dev/null +++ b/src/__tests__/native/prop-mapping.test.tsx @@ -0,0 +1,65 @@ +import { render, screen } from "@testing-library/react-native"; +import { transform } from "lightningcss"; +import { View } from "react-native-css/components"; +import { registerCSS } from "react-native-css/jest"; + +describe.each([false, true])("prop mapping (optimized: %s)", (minify) => { + test.each([ + ["-rn-native-mapping: test", { test: 37 }], + ["-rn-native-mapping: test.nested", { test: { nested: 37 } }], + ["-rn-native-mapping: &.test", { style: { test: 37 } }], + ["-rn-native-mapping-width: test", { test: 37 }], + [ + "-rn-native-mapping-width: &.test.nested", + { style: { test: { nested: 37 } } }, + ], + ])("%s", (mapping, expected) => { + const code = `.subject { width: 37px; ${mapping}; } .control { width: 29px; }`; + const compiled = registerCSS( + transform({ + filename: "test.css", + code: Buffer.from(code), + minify, + }).code.toString(), + ); + render( + <> + + + , + ); + expect(screen.getByTestId("subject").props).toMatchObject(expected); + expect(screen.getByTestId("subject").props.style?.width).toBeUndefined(); + expect(screen.getByTestId("control").props.style).toEqual({ width: 29 }); + expect(compiled.warnings()).toEqual({}); + }); + + test("multiple property mappings retain unmapped declarations", () => { + const code = `.subject { + -rn-native-mapping-width: test.width; + -rn-native-mapping-height: test.height; + width: 37px; height: 41px; opacity: 0.5; + }`; + registerCSS( + transform({ + filename: "test.css", + code: Buffer.from(code), + minify, + }).code.toString(), + ); + render(); + expect(screen.getByTestId("subject").props).toMatchObject({ + test: { width: 37, height: 41 }, + style: { opacity: 0.5 }, + }); + expect(screen.getByTestId("subject").props.style).toEqual({ opacity: 0.5 }); + }); +}); + +test("authored nativeMapping at rules remain supported", () => { + registerCSS( + `.subject { @nativeMapping { width: test.nested; } width: 37px; }`, + ); + render(); + expect(screen.getByTestId("subject").props.test).toEqual({ nested: 37 }); +}); diff --git a/src/__tests__/native/pseudo-classes.test.tsx b/src/__tests__/native/pseudo-classes.test.tsx index a41e9a23..f7d1c0e3 100644 --- a/src/__tests__/native/pseudo-classes.test.tsx +++ b/src/__tests__/native/pseudo-classes.test.tsx @@ -1,9 +1,82 @@ import { act, fireEvent, render, screen } from "@testing-library/react-native"; +import { Pressable } from "react-native-css/components/Pressable"; +import { TextInput } from "react-native-css/components/TextInput"; import { View } from "react-native-css/components/View"; import { registerCSS, testID } from "react-native-css/jest"; const children = undefined; +test.each([ + ["hover", "hoverIn", "hoverOut"], + ["focus", "focus", "blur"], + ["active", "pressIn", "pressOut"], +])("null event callbacks retain the %s state cycle", (state, enter, leave) => { + registerCSS(`.nullable { opacity: 0.5; } .nullable:${state} { opacity: 1; }`); + render( + , + ); + expect(screen.getByTestId(testID).props.style.opacity).toBe(0.5); + fireEvent(screen.getByTestId(testID), enter); + expect(screen.getByTestId(testID).props.style.opacity).toBe(1); + fireEvent(screen.getByTestId(testID), leave); + expect(screen.getByTestId(testID).props.style.opacity).toBe(0.5); +}); + +test("shared focus and blur callback preserves both style transitions", () => { + registerCSS(`.field { color: blue; } .field:focus { color: red; }`); + const callback = jest.fn(); + const replacement = jest.fn(); + const field = (handler: () => void) => ( + + ); + const view = render(field(callback)); + + for (const handler of [callback, replacement]) { + view.rerender(field(handler)); + expect(screen.getByTestId(testID).props.style.color).toBe("#00f"); + fireEvent(screen.getByTestId(testID), "focus"); + expect(screen.getByTestId(testID).props.style.color).toBe("#f00"); + fireEvent(screen.getByTestId(testID), "blur"); + expect(screen.getByTestId(testID).props.style.color).toBe("#00f"); + expect(handler).toHaveBeenCalledTimes(2); + } +}); + +test("shared hover callback preserves entering and leaving the component", () => { + registerCSS(`.tile { color: blue; } .tile:hover { color: red; }`); + const callback = jest.fn(); + render( + , + ); + for (let cycle = 0; cycle < 2; cycle++) { + fireEvent(screen.getByTestId(testID), "hoverIn"); + expect(screen.getByTestId(testID).props.style.color).toBe("#f00"); + fireEvent(screen.getByTestId(testID), "hoverOut"); + expect(screen.getByTestId(testID).props.style.color).toBe("#00f"); + } + expect(callback).toHaveBeenCalledTimes(4); +}); + test("hover", () => { registerCSS(` .text-color { diff --git a/src/__tests__/native/reactivity-dependencies.test.tsx b/src/__tests__/native/reactivity-dependencies.test.tsx new file mode 100644 index 00000000..52d4cd3b --- /dev/null +++ b/src/__tests__/native/reactivity-dependencies.test.tsx @@ -0,0 +1,106 @@ +import { + cleanupEffect, + family, + observable, + weakFamily, + type Effect, +} from "../../native/reactivity"; + +test.each([0, false, "", null, undefined, NaN])( + "families retain a cached %p until explicitly invalidated", + (result) => { + const create = jest.fn(() => result); + const cached = family(create); + expect(cached("first")).toBe(result); + expect(cached("first")).toBe(result); + expect(create).toHaveBeenCalledTimes(1); + cached("second"); + expect(create).toHaveBeenCalledTimes(2); + expect(cached.delete("first")).toBe(true); + cached("first"); + expect(create).toHaveBeenCalledTimes(3); + cached.clear(); + cached("first"); + cached("second"); + expect(create).toHaveBeenCalledTimes(5); + }, +); + +test.each([0, false, "", null, undefined, NaN])( + "weak families retain a cached %p for each key", + (result) => { + const create = jest.fn(() => result); + const cached = weakFamily(create); + const key = {}; + expect(cached.has(key)).toBe(false); + expect(cached(key)).toBe(result); + expect(cached.has(key)).toBe(true); + expect(cached(key)).toBe(result); + expect(create).toHaveBeenCalledTimes(1); + cached({}); + expect(create).toHaveBeenCalledTimes(2); + }, +); + +test("weak family identity numbering keeps its first zero identifier", () => { + let nextId = 0; + const identify = weakFamily(() => nextId++); + const first = {}; + const second = {}; + expect(identify(first)).toBe(0); + expect(identify(second)).toBe(1); + expect(identify(first)).toBe(0); + expect(identify(second)).toBe(1); + expect(nextId).toBe(2); +}); + +test("computed dependencies follow a conditional branch even when its value is unchanged", () => { + const first = observable(10); + const second = observable(10); + const selected = observable(true); + const compute = jest.fn((get: import("../../native/reactivity").Getter) => + get(selected) ? get(first) : get(second), + ); + const value = observable(compute); + const subscriber: Effect = { observers: new Set(), run: jest.fn() }; + expect(value.get(subscriber)).toBe(10); + expect(first.observers.size).toBe(1); + selected.set(false); + expect(value.get(subscriber)).toBe(10); + expect(first.observers.size).toBe(0); + expect(second.observers.size).toBe(1); + const calls = compute.mock.calls.length; + first.set(20); + expect(compute).toHaveBeenCalledTimes(calls); + second.set(30); + expect(value.get(subscriber)).toBe(30); + cleanupEffect(subscriber); + expect(selected.observers.size).toBe(0); + expect(second.observers.size).toBe(0); +}); + +test("explicit recomputation without subscribers does not leave a stale computed cache", () => { + const source = observable(10); + const value = observable((get) => get(source) * 2); + value.set(0); + expect(value.get()).toBe(20); + source.set(30); + expect(value.get()).toBe(60); + expect(source.observers.size).toBe(0); +}); + +test("an explicit computed argument replaces its previous dependency", () => { + const first = observable(10); + const second = observable(20); + const value = observable((get, useFirst = true) => + useFirst ? get(first) : get(second), + ); + const subscriber: Effect = { observers: new Set(), run: jest.fn() }; + expect(value.get(subscriber)).toBe(10); + value.set(false); + expect(value.get(subscriber)).toBe(20); + expect(first.observers.size).toBe(0); + expect(second.observers.size).toBe(1); + cleanupEffect(subscriber); + expect(second.observers.size).toBe(0); +}); diff --git a/src/__tests__/native/reactivity-lifecycle.test.tsx b/src/__tests__/native/reactivity-lifecycle.test.tsx new file mode 100644 index 00000000..4e3047f7 --- /dev/null +++ b/src/__tests__/native/reactivity-lifecycle.test.tsx @@ -0,0 +1,172 @@ +import { StrictMode } from "react"; +import { Text } from "react-native"; + +import { act, render } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; +import { useNativeVariable } from "react-native-css/native"; +import { + rootVariables, + StyleCollection, +} from "react-native-css/native-internal"; + +import { cleanupEffect, observable, vw } from "../../native/reactivity"; + +test("cleaned observers receive no later notifications", () => { + const value = observable(0); + const effect = { + observers: new Set>(), + run: jest.fn(), + }; + value.get(effect); + value.set(1); + expect(effect.run).toHaveBeenCalledTimes(1); + cleanupEffect(effect); + value.set(2); + expect(effect.run).toHaveBeenCalledTimes(1); + expect(value.observers.size).toBe(0); +}); + +test("native variable hook releases its subscription on unmount", () => { + const value = rootVariables("audit-hook-lifecycle"); + value.set([[10]]); + const before = value.observers.size; + function Sample() { + const current = useNativeVariable("--audit-hook-lifecycle"); + return {String(current)}; + } + const screen = render(); + expect(screen.getByTestId("value").props.children).toBe("10"); + act(() => { + value.set([[20]]); + }); + expect(screen.getByTestId("value").props.children).toBe("20"); + screen.unmount(); + expect(value.observers.size).toBe(before); +}); + +test("viewport styles release their subscription on unmount", () => { + registerCSS(".audit-lifecycle { width: 10vw; }"); + const before = vw.observers.size; + const screen = render(); + expect(screen.getByTestId("sample").props.style.width).toBeGreaterThan(0); + expect(vw.observers.size).toBeGreaterThan(before); + screen.unmount(); + expect(vw.observers.size).toBe(before); +}); + +test("variable subscriptions follow a changed variable name", () => { + const first = rootVariables("audit-first"); + const second = rootVariables("audit-second"); + first.set([[10]]); + second.set([[30]]); + function Sample({ name }: { name: string }) { + return {String(useNativeVariable(name))}; + } + const screen = render(); + expect(first.observers.size).toBe(1); + screen.rerender(); + expect(screen.getByTestId("value").props.children).toBe("30"); + expect(first.observers.size).toBe(0); + act(() => { + second.set([[40]]); + }); + expect(screen.getByTestId("value").props.children).toBe("40"); + screen.unmount(); + expect(second.observers.size).toBe(0); +}); + +test("a shared stylesheet remains reactive until its final consumer unmounts", () => { + registerCSS(".audit-shared { width: 10vw; }"); + const original = vw.get(); + const before = vw.observers.size; + const first = render(); + const second = render(); + first.unmount(); + act(() => { + vw.set(500); + }); + expect(second.getByTestId("second").props.style.width).toBe(50); + second.unmount(); + expect(vw.observers.size).toBe(before); + act(() => { + vw.set(original); + }); +}); + +test("a remounted viewport consumer reads changes made while unmounted", () => { + registerCSS(".audit-remount { width: 10vw; }"); + const original = vw.get(); + const first = render(); + first.unmount(); + act(() => { + vw.set(600); + }); + const second = render(); + expect(second.getByTestId("sample").props.style.width).toBe(60); + second.unmount(); + act(() => { + vw.set(original); + }); +}); + +test("StrictMode variable consumers remain reactive and release subscriptions", () => { + const value = rootVariables("audit-strict-variable"); + value.set([[10]]); + function Sample() { + return ( + + {String(useNativeVariable("audit-strict-variable"))} + + ); + } + const screen = render( + + + , + ); + act(() => { + value.set([[20]]); + }); + expect(screen.getByTestId("value").props.children).toBe("20"); + screen.unmount(); + expect(value.observers.size).toBe(0); +}); + +test("StrictMode viewport consumers remain reactive and release subscriptions", () => { + registerCSS(".audit-strict-width { width: 10vw; }"); + const rules = StyleCollection.styles("audit-strict-width"); + const ruleCount = rules.observers.size; + const original = vw.get(); + const before = vw.observers.size; + const screen = render( + + + , + ); + act(() => { + vw.set(700); + }); + expect(screen.getByTestId("sample").props.style.width).toBe(70); + screen.unmount(); + expect(rules.observers.size).toBe(ruleCount); + expect(vw.observers.size).toBe(before); + act(() => { + vw.set(original); + }); +}); + +test("class changes detach the old rule subscription", () => { + registerCSS( + ".audit-rule-first { width: 10px; } .audit-rule-second { width: 20px; }", + ); + const first = StyleCollection.styles("audit-rule-first"); + const second = StyleCollection.styles("audit-rule-second"); + const screen = render(); + expect(first.observers.size).toBe(1); + screen.rerender(); + expect(screen.getByTestId("sample").props.style.width).toBe(20); + expect(first.observers.size).toBe(0); + screen.unmount(); + expect(second.observers.size).toBe(0); +}); diff --git a/src/__tests__/native/selectors.test.tsx b/src/__tests__/native/selectors.test.tsx index 92824ca4..f15893af 100644 --- a/src/__tests__/native/selectors.test.tsx +++ b/src/__tests__/native/selectors.test.tsx @@ -3,68 +3,69 @@ import { View } from "react-native-css/components/View"; import { registerCSS, testID } from "react-native-css/jest"; import { colorScheme } from "react-native-css/runtime"; -test.skip(":is(.dark *)", () => { - registerCSS(`@cssInterop set darkMode class dark; -.my-class:is(.dark *) { color: red; }`); - - render(); - - const component = screen.getByTestId(testID); - - expect(component.props.style).toStrictEqual(undefined); - - act(() => { - colorScheme.set("dark"); - }); - - expect(component.props.style).toStrictEqual({ color: "#f00" }); +test("legacy class dark mode configuration is rejected with a migration path", () => { + expect(() => + registerCSS( + `@cssInterop set darkMode class dark; .my-class:is(.dark *) { color: red; }`, + ), + ).toThrow(/prefers-color-scheme/); }); -test.skip(':root[class="dark"]', () => { - registerCSS(`@cssInterop set darkMode class dark; -:root[class="dark"] { - --my-var: red; -} -.my-class { - color: var(--my-var); -}`); - - render(); - - const component = screen.getByTestId(testID); - - expect(component.props.style).toStrictEqual({}); - - act(() => { - colorScheme.set("dark"); - }); - - expect(component.props.style).toStrictEqual({ color: "red" }); +test.each([':root[class="dark"]', ':root[class~="dark"]'])( + "legacy qualified root %s cannot silently become unconditional", + (selector) => { + expect(() => + registerCSS( + `${selector} { --my-var: red; } .my-class { color: var(--my-var); }`, + ), + ).toThrow(/prefers-color-scheme/); + }, +); + +test("explicit ancestor selector follows its actual class and restores when removed", () => { + registerCSS( + `.my-class { color: blue; } .my-class:is(.dark *) { color: red; }`, + ); + const tree = (className: string) => ( + + + + ); + render(tree("")); + expect(screen.getByTestId(testID).props.style).toEqual({ color: "#00f" }); + screen.rerender(tree("dark")); + expect(screen.getByTestId(testID).props.style).toEqual({ color: "#f00" }); + screen.rerender(tree("")); + expect(screen.getByTestId(testID).props.style).toEqual({ color: "#00f" }); }); -test.skip(':root[class~="dark"]', () => { - registerCSS(` - @react-native { - darkMode: dark; - } - - :root[class~="dark"] { - --my-var: red; - } - .my-class { - color: var(--my-var); - } - `); - - render(); - - const component = screen.getByTestId(testID); - - expect(component.props.style).toStrictEqual({}); - - act(() => { - colorScheme.set("dark"); - }); - - expect(component.props.style).toStrictEqual({ color: "red" }); +test.each([undefined, false] as const)( + "root variables track system dark mode with inlineVariables=%s", + (inlineVariables) => { + registerCSS( + `:root { --my-var: blue; } @media (prefers-color-scheme: dark) { :root { --my-var: red; } } .my-class { color: var(--my-var); }`, + { inlineVariables }, + ); + act(() => { + colorScheme.set("light"); + }); + render(); + expect(screen.getByTestId(testID).props.style).toEqual({ color: "blue" }); + act(() => { + colorScheme.set("dark"); + }); + expect(screen.getByTestId(testID).props.style).toEqual({ color: "red" }); + act(() => { + colorScheme.set("light"); + }); + expect(screen.getByTestId(testID).props.style).toEqual({ color: "blue" }); + }, +); + +test("legacy inline compiler options are rejected with their supported replacement", () => { + expect(() => + registerCSS( + `@react-native config { preserve-variables: --green; } .test { --green: green; }`, + ), + ).toThrow(/inlineVariables/); }); diff --git a/src/__tests__/native/styled.test.ios.tsx b/src/__tests__/native/styled.test.ios.tsx index 79ea21fd..3f4ef620 100644 --- a/src/__tests__/native/styled.test.ios.tsx +++ b/src/__tests__/native/styled.test.ios.tsx @@ -1,12 +1,14 @@ -import { View } from "react-native"; +import { StyleSheet, View, type StyleProp, type ViewStyle } from "react-native"; import { render, screen } from "@testing-library/react-native"; +import { VariableContextProvider } from "react-native-css"; +import { View as CssView } from "react-native-css/components/View"; import { registerCSS, testID } from "react-native-css/jest"; import { styled } from "react-native-css/runtime"; const children = undefined; -test.skip("static styles w/ only target", () => { +test("static styles w/ only target", () => { registerCSS(` .text-blue-500 { color: blue; @@ -26,12 +28,12 @@ test.skip("static styles w/ only target", () => { testID, children, style: { - color: "#0000ff", + color: "#00f", }, }); }); -test.skip("static styles w/ target & nativeStyleMapping", () => { +test("static styles w/ target & nativeStyleMapping", () => { registerCSS(` .text-blue-500 { color: blue; @@ -57,14 +59,14 @@ test.skip("static styles w/ target & nativeStyleMapping", () => { expect(component.props).toStrictEqual({ testID, children, - myColor: "#0000ff", + myColor: "#00f", other: { - backgroundColor: "#ff0000", + backgroundColor: "#f00", }, }); }); -test.skip("static styles w/ target none", () => { +test("static styles w/ target none", () => { registerCSS(` .text-blue-500 { color: blue; @@ -90,11 +92,11 @@ test.skip("static styles w/ target none", () => { expect(component.props).toStrictEqual({ testID, children, - myColor: "#0000ff", + myColor: "#00f", }); }); -test.skip("dynamic styles w/ target & nativeStyleToProp", () => { +test("dynamic styles w/ target & nativeStyleToProp", () => { registerCSS(` .text-blue-500 { --blue: blue; @@ -122,9 +124,247 @@ test.skip("dynamic styles w/ target & nativeStyleToProp", () => { expect(component.props).toStrictEqual({ testID, children, - myColor: "blue", + myColor: "#00f", other: { - backgroundColor: "red", + backgroundColor: "#f00", }, }); }); + +// Both spellings are part of the declared API during migration. +test.each(["nativeStyleMapping", "nativeStyleToProp"] as const)( + "%s updates, removes, and restores mapped values without discarding inline styles", + (option) => { + registerCSS(`.first { color: blue; background-color: red; } + .second { color: green; background-color: red; }`); + const StyledView = styled(View, { + className: { target: false, [option]: { color: "accessibilityLabel" } }, + }); + render(); + for (const [className, expected] of [ + ["first", "#00f"], + ["second", "#008000"], + ["", undefined], + ["first", "#00f"], + ] as const) { + const element = ( + + ); + screen.rerender(element); + expect(screen.getByTestId(testID).props.accessibilityLabel).toBe( + expected, + ); + expect(screen.getByTestId(testID).props.style).toEqual({ opacity: 0.4 }); + } + }, +); + +test.each([false, true])( + "current mapping wins over deprecated alias (empty: %s)", + (empty) => { + registerCSS(`.subject { color: blue; }`); + const StyledView = styled(View, { + className: { + target: "style", + nativeStyleMapping: empty ? {} : { color: "accessibilityLabel" }, + nativeStyleToProp: { color: "testID" }, + }, + }); + render(); + const props = screen.getByTestId(testID).props; + expect(props.accessibilityLabel).toBe(empty ? undefined : "#00f"); + if (empty) expect(props.style).toEqual({ color: "#00f" }); + }, +); + +test("passThrough resolves three segment targets and updates without leaking root props", () => { + registerCSS(`.first { width: 40px; } .second { width: 80px; }`); + interface Props { + nested?: { inner?: { style?: StyleProp } }; + } + const Base = (props: Props) => { + expect(props).not.toHaveProperty("inner"); + return ; + }; + const Wrapped = styled( + Base, + { className: "nested.inner.style" }, + { passThrough: true }, + ); + render(); + for (const [className, expected] of [ + ["first", 40], + ["second", 80], + [undefined, undefined], + ["first", 40], + ] as const) { + screen.rerender(); + expect( + StyleSheet.flatten(screen.getByTestId(testID).props.style)?.width, + ).toBe(expected); + } +}); + +test("passThrough preserves caller owned nested props and inline style arrays", () => { + registerCSS(`.first { width: 40px; } .second { width: 80px; }`); + interface Props { + nested: { style?: StyleProp; label: string }; + } + const Base = ({ nested }: Props) => ( + + ); + const Wrapped = styled( + Base, + { className: "nested.style" }, + { passThrough: true }, + ); + const styles = [{ height: 12 }, { opacity: 0.5 }]; + const nested = Object.freeze({ style: styles, label: "preserved" }); + render(); + for (const [className, expected] of [ + ["first", 40], + ["second", 80], + [undefined, undefined], + ["first", 40], + ] as const) { + screen.rerender(); + const props = screen.getByTestId(testID).props; + expect(StyleSheet.flatten(props.style)).toMatchObject({ + height: 12, + opacity: 0.5, + }); + expect(StyleSheet.flatten(props.style).width).toBe(expected); + expect(props.accessibilityLabel).toBe("preserved"); + expect(nested.style).toBe(styles); + expect(styles).toEqual([{ height: 12 }, { opacity: 0.5 }]); + } +}); + +test("passThrough default mapping resolves variables in the receiving component context", () => { + registerCSS(`.deferred { width: var(--deferred-size); }`); + interface Props { + size: number; + style?: StyleProp; + } + const Base = ({ size, style }: Props) => ( + + + + ); + const Wrapped = styled(Base, undefined, { passThrough: true }); + render(); + expect(StyleSheet.flatten(screen.getByTestId(testID).props.style)).toEqual({ + width: 40, + height: 12, + }); + screen.rerender( + , + ); + expect(StyleSheet.flatten(screen.getByTestId(testID).props.style)).toEqual({ + width: 80, + height: 12, + }); +}); + +test("passThrough false target consumes its source without changing ordinary props", () => { + registerCSS(`.discarded { width: 40px; }`); + interface Props { + style?: StyleProp; + accessibilityLabel?: string; + } + const Base = (props: Props) => { + expect(props).not.toHaveProperty("className"); + return ; + }; + const Wrapped = styled( + Base, + { className: { target: false } }, + { passThrough: true }, + ); + render( + , + ); + const props = screen.getByTestId(testID).props; + expect(StyleSheet.flatten(props.style)).toEqual({ height: 12 }); + expect(props.accessibilityLabel).toBe("preserved"); +}); + +test("independent nested mappings retain both inline and class styles", () => { + registerCSS(`.first { width: 40px; } .second { width: 80px; }`); + interface Props { + a?: { style?: StyleProp }; + b?: { style?: StyleProp }; + } + const Base = ({ a, b }: Props) => ( + <> + + + + ); + const Wrapped = styled(Base, { + firstClass: "a.style", + secondClass: "b.style", + }); + const a = Object.freeze({ style: { height: 12 } }); + const b = Object.freeze({ style: { height: 24 } }); + render(); + for (const [firstClass, secondClass, firstWidth, secondWidth] of [ + ["first", "second", 40, 80], + ["second", "first", 80, 40], + [undefined, undefined, undefined, undefined], + ["first", "second", 40, 80], + ] as const) { + screen.rerender( + , + ); + expect( + StyleSheet.flatten(screen.getByTestId("mapped-a").props.style), + ).toEqual({ + height: 12, + ...(firstWidth === undefined ? {} : { width: firstWidth }), + }); + expect( + StyleSheet.flatten(screen.getByTestId("mapped-b").props.style), + ).toEqual({ + height: 24, + ...(secondWidth === undefined ? {} : { width: secondWidth }), + }); + expect(a).toEqual({ style: { height: 12 } }); + expect(b).toEqual({ style: { height: 24 } }); + } +}); + +test("three segment native target does not leak intermediate keys into root props", () => { + registerCSS(`.subject { width: 40px; }`); + interface Props { + outer?: { inner?: { style?: StyleProp } }; + } + const Base = (props: Props) => { + expect(props).not.toHaveProperty("inner"); + expect(props).not.toHaveProperty("style"); + return ; + }; + const Wrapped = styled(Base, { className: "outer.inner.style" }); + render( + , + ); + expect(StyleSheet.flatten(screen.getByTestId(testID).props.style)).toEqual({ + width: 40, + height: 12, + }); +}); diff --git a/src/__tests__/native/transform.test.tsx b/src/__tests__/native/transform.test.tsx index b7a08fca..7a59b816 100644 --- a/src/__tests__/native/transform.test.tsx +++ b/src/__tests__/native/transform.test.tsx @@ -56,7 +56,7 @@ describe("scale", () => { ).getByTestId(testID); expect(component.props.style).toStrictEqual({ - transform: [{ scaleX: "2%" }, { scaleY: "2%" }], + transform: [{ scaleX: 0.02 }, { scaleY: 0.02 }], }); }); diff --git a/src/__tests__/native/transitions.test.tsx b/src/__tests__/native/transitions.test.tsx index f29cb799..d563156c 100644 --- a/src/__tests__/native/transitions.test.tsx +++ b/src/__tests__/native/transitions.test.tsx @@ -1,148 +1,53 @@ +import { StyleSheet } from "react-native"; + import { render, screen } from "@testing-library/react-native"; import { View } from "react-native-css/components/View"; import { registerCSS, testID } from "react-native-css/jest"; -// import { getAnimatedStyle } from "react-native-reanimated"; - -const getAnimatedStyle = (..._args: unknown[]): unknown => { - return; -}; - -jest.useFakeTimers(); - -describe.skip("transitions", () => { - test("basic transition", () => { - registerCSS(` - .transition-width { - transition-property: width; - transition-duration: 1s; - } - - .width-1 { - width: 100; - } - `); - - render(); - - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({}); - - // Nothing should happen - jest.advanceTimersByTime(500); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({}); - - screen.rerender( - , - ); - - // Transitions start once the useEffect() runs - jest.advanceTimersToNextTimer(); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ width: 0 }); - - // Check progress of transition - jest.advanceTimersByTime(500); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ width: 50 }); - - // Check it ends - jest.advanceTimersByTime(500); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ - width: 100, - }); - - // And doesn't continue - jest.advanceTimersByTime(500); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ - width: 100, - }); - }); - - test("updating transition", () => { - registerCSS(` - .transition-width { - transition-property: width; - transition-duration: 1s; - } - - .width-1 { - width: 100; - } - - .width-2 { - width: 200; - } - `); - - render(); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({}); - - screen.rerender( - , - ); - // Transitions start once the useEffect() runs - jest.advanceTimersToNextTimer(); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ width: 0 }); - - // Check progress of transition - jest.advanceTimersByTime(1000); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ - width: 100, - }); - - screen.rerender( - , - ); - jest.advanceTimersToNextTimer(); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ - width: 100, - }); - - jest.advanceTimersByTime(500); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ - width: 150, - }); +// Observe the engine's transition contract before Reanimated consumes it. +// Intermediate frame and final rendering assertions belong to the native suite. +jest.mock("../../native/reanimated", () => ({ + animatedComponentFamily: (component: unknown) => component, +})); +const style = () => + StyleSheet.flatten(screen.getByTestId(testID).props.style) as + | Record + | undefined; +beforeEach(() => + registerCSS( + `.motion { transition: width 1s linear; } .first { width: 100px; } .second { width: 200px; }`, + ), +); + +test("basic transition supplies metadata and the changed target", () => { + render(); + expect(style()).toMatchObject({ + transitionProperty: ["width"], + transitionDuration: [1000], + transitionDelay: [0], + transitionTimingFunction: "linear", }); + expect(style()?.width).toBeUndefined(); + screen.rerender(); + expect(style()).toMatchObject({ width: 100, transitionDuration: [1000] }); +}); - test("removing transition", () => { - registerCSS(` - .transition-width { - transition-property: width; - transition-duration: 1s; - } - - .width-1 { - width: 100; - } - - .width-2 { - width: 200; - } - `); - - render(); - - render(); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({}); - - screen.rerender( - , - ); - // Transitions start once the useEffect() runs - jest.advanceTimersToNextTimer(); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ width: 0 }); - - // Check progress of transition - jest.advanceTimersByTime(1000); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ - width: 100, - }); - - screen.rerender(); - jest.advanceTimersToNextTimer(); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ - width: 100, - }); +test("updating transition target does not retain the previous value", () => { + render(); + expect(style()?.width).toBe(100); + screen.rerender(); + expect(style()).toMatchObject({ width: 200, transitionProperty: ["width"] }); +}); - jest.advanceTimersByTime(500); - expect(getAnimatedStyle(screen.getByTestId(testID))).toEqual({ width: 50 }); - }); +test("removing a target or transition clears its fields and supports restoration", () => { + render(); + screen.rerender(); + expect(style()?.width).toBeUndefined(); + expect(style()?.transitionDuration).toEqual([1000]); + screen.rerender(); + expect(style()?.width).toBe(200); + expect(style()?.transitionProperty).toBeUndefined(); + expect(style()?.transitionDuration).toBeUndefined(); + screen.rerender(); + expect(style()).toMatchObject({ width: 100, transitionDuration: [1000] }); }); diff --git a/src/__tests__/native/universal-variables.test.tsx b/src/__tests__/native/universal-variables.test.tsx new file mode 100644 index 00000000..a1675cce --- /dev/null +++ b/src/__tests__/native/universal-variables.test.tsx @@ -0,0 +1,94 @@ +import { act, render, screen } from "@testing-library/react-native"; +import { VariableContextProvider } from "react-native-css"; +import { compile } from "react-native-css/compiler"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; +import { + rootVariables, + StyleCollection, + universalVariables, +} from "react-native-css/native-internal"; + +test("universal variables do not overwrite the root variable store", () => { + registerCSS( + `:root { --audit-universal-store: 10px; } * { --audit-universal-store: 20px; }`, + { inlineVariables: false }, + ); + expect(rootVariables("audit-universal-store").get()).toBe(10); + expect(universalVariables("audit-universal-store").get()).toBe(20); +}); + +test("a universal declaration overrides an inherited class variable", () => { + registerCSS( + ` + * { --audit-universal-parent: 20px; } + .parent { --audit-universal-parent: 40px; } + .child { width: var(--audit-universal-parent); } + `, + { inlineVariables: false }, + ); + render( + + + , + ); + expect(screen.getByTestId("child").props.style).toStrictEqual({ width: 20 }); +}); + +test("a class declaration overrides a universal declaration", () => { + registerCSS( + ` + * { --audit-universal-local: 20px; } + .child { --audit-universal-local: 60px; width: var(--audit-universal-local); } + `, + { inlineVariables: false }, + ); + render(); + expect(screen.getByTestId("child").props.style).toStrictEqual({ width: 60 }); +}); + +test("a universal declaration overrides a variable inherited from a provider", () => { + registerCSS( + ` + * { --audit-universal-provider: 20px; } + .child { width: var(--audit-universal-provider); } + `, + { inlineVariables: false }, + ); + render( + + + , + ); + expect(screen.getByTestId("child").props.style).toStrictEqual({ width: 20 }); +}); + +test("mounted universal variable consumers update and restore without changing the root", () => { + registerCSS( + ` + :root { --audit-universal-update: 10px; } + * { --audit-universal-update: 20px; } + .child { width: var(--audit-universal-update); } + `, + { inlineVariables: false }, + ); + render(); + expect(screen.getByTestId("child").props.style).toStrictEqual({ width: 20 }); + act(() => { + StyleCollection.inject( + compile(`* { --audit-universal-update: 30px; }`, { + inlineVariables: false, + }).stylesheet(), + ); + }); + expect(screen.getByTestId("child").props.style).toStrictEqual({ width: 30 }); + act(() => { + StyleCollection.inject( + compile(`* { --audit-universal-update: 20px; }`, { + inlineVariables: false, + }).stylesheet(), + ); + }); + expect(screen.getByTestId("child").props.style).toStrictEqual({ width: 20 }); + expect(rootVariables("audit-universal-update").get()).toBe(10); +}); diff --git a/src/__tests__/native/variable-cycles.test.tsx b/src/__tests__/native/variable-cycles.test.tsx new file mode 100644 index 00000000..0bcf5ef7 --- /dev/null +++ b/src/__tests__/native/variable-cycles.test.tsx @@ -0,0 +1,49 @@ +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS } from "react-native-css/jest"; + +// Current CSS substitution rules ignore unused fallback cycles. +// https://github.com/w3c/csswg-drafts/issues/11500 +test.each([ + ["self reference", "--a: var(--a)", 37], + ["self reference with an internal fallback", "--a: var(--a, 12px)", 37], + ["mutual reference", "--a: var(--b); --b: var(--a)", 37], + [ + "mutual reference with an internal fallback", + "--a: var(--b); --b: var(--a, 12px)", + 37, + ], + ["valid chain", "--a: var(--b); --b: 20px", 20], + ["missing variable fallback", "--b: 20px", 37], + ["cycle in an unused fallback", "--a: var(--b, var(--a)); --b: 20px", 20], + [ + "acyclic fallback dependency", + "--a: var(--b, var(--c)); --b: 20px; --c: 30px", + 20, + ], +])("resolves %s without recursive failure", (_name, declarations, expected) => { + registerCSS(`.sample { ${declarations}; width: var(--a, 37px); }`, { + inlineVariables: false, + }); + render(); + expect(screen.getByTestId("sample").props.style).toStrictEqual({ + width: expected, + }); +}); + +test("a variable outside a cycle can use a fallback for the invalid variable", () => { + registerCSS( + `.sample { --a: var(--b); --b: var(--a); --c: var(--a, 42px); width: var(--c); }`, + { inlineVariables: false }, + ); + render(); + expect(screen.getByTestId("sample").props.style).toStrictEqual({ width: 42 }); +}); + +test("repeated sibling variable references are not cycles", () => { + registerCSS(`.sample { --a: 20px; width: calc(var(--a) + var(--a)); }`, { + inlineVariables: false, + }); + render(); + expect(screen.getByTestId("sample").props.style).toStrictEqual({ width: 40 }); +}); diff --git a/src/__tests__/vendor/tailwind/backgrounds.test.tsx b/src/__tests__/vendor/tailwind/backgrounds.test.tsx index 08e80e52..c0d66c38 100644 --- a/src/__tests__/vendor/tailwind/backgrounds.test.tsx +++ b/src/__tests__/vendor/tailwind/backgrounds.test.tsx @@ -192,7 +192,7 @@ describe("Backgrounds - Background Image", () => { ).toStrictEqual({ props: { style: { - experimental_backgroundImage: ["none"], + experimental_backgroundImage: "none", }, }, }); diff --git a/src/__tests__/vendor/tailwind/borders.test.tsx b/src/__tests__/vendor/tailwind/borders.test.tsx index 48f065c7..6de1c0be 100644 --- a/src/__tests__/vendor/tailwind/borders.test.tsx +++ b/src/__tests__/vendor/tailwind/borders.test.tsx @@ -35,8 +35,8 @@ describe("Border - Border Width", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - borderInlineWidth: 1, - borderInlineStyle: "solid", + borderStartWidth: 1, + borderEndWidth: 1, }, }, }); @@ -45,8 +45,8 @@ describe("Border - Border Width", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - borderBlockWidth: 1, - borderBlockStyle: "solid", + borderTopWidth: 1, + borderBottomWidth: 1, }, }, }); @@ -54,14 +54,14 @@ describe("Border - Border Width", () => { test("border-s-1", async () => { expect(await renderCurrentTest()).toStrictEqual({ props: { - style: { borderInlineStartWidth: 1, borderInlineStartStyle: "solid" }, + style: { borderStartWidth: 1 }, }, }); }); test("border-e-1", async () => { expect(await renderCurrentTest()).toStrictEqual({ props: { - style: { borderInlineEndWidth: 1, borderInlineEndStyle: "solid" }, + style: { borderEndWidth: 1 }, }, }); }); @@ -98,8 +98,8 @@ describe("Border - Border Width", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - borderInlineWidth: 2, - borderInlineStyle: "solid", + borderStartWidth: 2, + borderEndWidth: 2, }, }, }); @@ -108,8 +108,8 @@ describe("Border - Border Width", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - borderBlockWidth: 2, - borderBlockStyle: "solid", + borderTopWidth: 2, + borderBottomWidth: 2, }, }, }); @@ -117,14 +117,14 @@ describe("Border - Border Width", () => { test("border-s-2", async () => { expect(await renderCurrentTest()).toStrictEqual({ props: { - style: { borderInlineStartWidth: 2, borderInlineStartStyle: "solid" }, + style: { borderStartWidth: 2 }, }, }); }); test("border-e-2", async () => { expect(await renderCurrentTest()).toStrictEqual({ props: { - style: { borderInlineEndWidth: 2, borderInlineEndStyle: "solid" }, + style: { borderEndWidth: 2 }, }, }); }); @@ -160,7 +160,8 @@ describe("Border - Border Color", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - borderInlineColor: "#fff", + borderStartColor: "#fff", + borderEndColor: "#fff", }, }, }); @@ -219,8 +220,8 @@ describe("Border - Border Color", () => { ).toStrictEqual({ props: { style: { - borderLeftColor: "#fb2c36", - borderRightColor: "#fb2c36", + borderStartColor: "#fb2c36", + borderEndColor: "#fb2c36", color: "#fb2c36", }, }, diff --git a/src/__tests__/vendor/tailwind/filters.test.tsx b/src/__tests__/vendor/tailwind/filters.test.tsx index c7bbb4a6..60d5ca93 100644 --- a/src/__tests__/vendor/tailwind/filters.test.tsx +++ b/src/__tests__/vendor/tailwind/filters.test.tsx @@ -60,24 +60,22 @@ describe("Filters - Drop Shadow", () => { props: { style: { filter: [ - [ - { - dropShadow: { - standardDeviation: 2, - color: "#0000001a", - offsetX: 0, - offsetY: 1, - }, + { + dropShadow: { + standardDeviation: 2, + color: "#0000001a", + offsetX: 0, + offsetY: 1, }, - { - dropShadow: { - standardDeviation: 1, - color: "#0000000f", - offsetX: 0, - offsetY: 1, - }, + }, + { + dropShadow: { + standardDeviation: 1, + color: "#0000000f", + offsetX: 0, + offsetY: 1, }, - ], + }, ], }, }, diff --git a/src/__tests__/vendor/tailwind/transform.test.ts b/src/__tests__/vendor/tailwind/transform.test.ts index a056663f..2a7cf239 100644 --- a/src/__tests__/vendor/tailwind/transform.test.ts +++ b/src/__tests__/vendor/tailwind/transform.test.ts @@ -26,7 +26,7 @@ describe("Transforms - Scale", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ scale: "0%" }], + transform: [{ scale: 0 }], }, }, }); @@ -35,7 +35,7 @@ describe("Transforms - Scale", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ scaleX: "50%" }, { scaleY: 1 }], + transform: [{ scaleX: 0.5 }, { scaleY: 1 }], }, }, }); @@ -44,7 +44,7 @@ describe("Transforms - Scale", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ scaleX: 1 }, { scaleY: "50%" }], + transform: [{ scaleX: 1 }, { scaleY: 0.5 }], }, }, }); @@ -53,7 +53,7 @@ describe("Transforms - Scale", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ scale: "50%" }], + transform: [{ scale: 0.5 }], }, }, }); diff --git a/src/__tests__/web/mapping.test.tsx b/src/__tests__/web/mapping.test.tsx new file mode 100644 index 00000000..d4fa13ec --- /dev/null +++ b/src/__tests__/web/mapping.test.tsx @@ -0,0 +1,91 @@ +import { type StyleProp, type ViewStyle } from "react-native"; + +import { useCssElement } from "../../web/api"; +import { assignStyle } from "../../web/assign-style"; + +interface Props { + testID?: string; + className?: string; + style?: + | StyleProp + | ((state: { pressed: boolean }) => StyleProp); + nested?: { + child?: { style?: StyleProp }; + slots?: { style?: StyleProp }[]; + }; +} +const Base = (_props: Props) => null; + +test("web mapping preserves frozen caller owned nested props", () => { + const original = Object.freeze({ + child: Object.freeze({ style: Object.freeze({ opacity: 0.5 }) }), + }); + const props = { nested: original, className: "p-4", testID: "test" }; + const element = useCssElement(Base, props, { + className: "nested.child.style", + }); + expect(element.props).toEqual({ + testID: "test", + nested: { + child: { style: [{ opacity: 0.5 }, { $$css: true, className: "p-4" }] }, + }, + }); + expect(props).toEqual({ + nested: { child: { style: { opacity: 0.5 } } }, + className: "p-4", + testID: "test", + }); + expect(element.props.nested).not.toBe(original); +}); + +test("web mapping preserves nested arrays and unrelated entries", () => { + const first = Object.freeze({ style: Object.freeze({ opacity: 0.5 }) }); + const second = Object.freeze({ style: Object.freeze({ width: 20 }) }); + const slots = Object.freeze([first, second]); + const element = { + props: assignStyle( + { $$css: true, className: "p-4" }, + ["nested", "slots", "0", "style"], + { nested: { slots } }, + ), + }; + expect(Array.isArray(element.props.nested.slots)).toBe(true); + expect(element.props.nested.slots[0].style).toEqual([ + { opacity: 0.5 }, + { $$css: true, className: "p-4" }, + ]); + expect(element.props.nested.slots[1]).toBe(second); + expect(slots[0]).toBe(first); +}); + +test("web callback styles preserve arguments and inline styles", () => { + const callback = jest.fn((state: { pressed: boolean }) => ({ + opacity: state.pressed ? 0.5 : 1, + })); + const props = { style: callback, className: "p-4" }; + const element = useCssElement(Base, props, { className: "style" }); + const evaluate = element.props.style as (state: { + pressed: boolean; + }) => unknown; + expect(evaluate({ pressed: true })).toEqual([ + { opacity: 0.5 }, + { $$css: true, className: "p-4" }, + ]); + expect(callback).toHaveBeenCalledWith({ pressed: true }); + expect(props.style).toBe(callback); +}); + +test("web class updates and removal preserve the original inline array", () => { + const style = [{ opacity: 0.5 }]; + for (const className of ["p-4", "p-8", undefined, "p-4"]) { + const element = useCssElement( + Base, + { style, className }, + { className: "style" }, + ); + expect(element.props.style).toEqual( + className ? [style, { $$css: true, className }] : style, + ); + expect(style).toEqual([{ opacity: 0.5 }]); + } +}); diff --git a/src/babel/import-plugin.ts b/src/babel/import-plugin.ts index 7c7c0ac0..b199d8ca 100644 --- a/src/babel/import-plugin.ts +++ b/src/babel/import-plugin.ts @@ -1,4 +1,4 @@ -import { resolve } from "path"; +import { basename, dirname, resolve, sep } from "path"; import { type PluginObj } from "@babel/core"; import type { Statement } from "@babel/types"; @@ -16,6 +16,7 @@ import { import { handleReactNativeWebIdentifierRequire, handleReactNativeWebImport, + handleReactNativeWebInteropRequireDefault, handleReactNativeWebObjectPatternRequire, } from "./react-native-web"; @@ -26,8 +27,12 @@ export default function ({ }): PluginObj { const processed = new WeakSet(); - const thisModuleDist = resolve(__dirname, "../../../dist"); - const thisModuleSrc = resolve(__dirname, "../../../src"); + const packageRoot = resolve( + __dirname, + basename(dirname(__dirname)) === "src" ? "../.." : "../../..", + ); + const thisModuleDist = resolve(packageRoot, "dist") + sep; + const thisModuleSrc = resolve(packageRoot, "src") + sep; function isFromThisModule(filename: string): boolean { return ( @@ -94,6 +99,11 @@ export default function ({ return; } + // A local function called require is not the module loader. + if (path.scope.getBinding("require")) { + return; + } + const initArg = init.arguments.at(0); if (!initArg) { @@ -152,10 +162,11 @@ export default function ({ if (!source) { return; } - statements = handleReactNativeWebIdentifierRequire( + statements = handleReactNativeWebInteropRequireDefault( path, t, id.name, + init, source, state.filename, ); diff --git a/src/babel/react-native-web.ts b/src/babel/react-native-web.ts index 1bacf5a0..2b1d77a5 100644 --- a/src/babel/react-native-web.ts +++ b/src/babel/react-native-web.ts @@ -1,7 +1,8 @@ -import { resolve } from "path"; +import { dirname, resolve } from "path"; import { type NodePath } from "@babel/traverse"; import tBabelTypes, { + type CallExpression, type ImportDeclaration, type ObjectPattern, type Statement, @@ -14,7 +15,7 @@ type BabelTypes = typeof tBabelTypes; function parseReactNativeWebSource(source: string, filename: string) { if (source.startsWith(".")) { - source = resolve(filename, source); + source = resolve(dirname(filename), source); const internalPath = source.split("react-native-web/dist")[1]; if (!internalPath) { @@ -48,6 +49,7 @@ export function handleReactNativeWebImport( filename: string, ): Statement[] | undefined { const { specifiers, source } = declaration; + if (declaration.importKind && declaration.importKind !== "value") return; const rnwSource = parseReactNativeWebSource(source.value, filename); if (!rnwSource) { @@ -76,7 +78,7 @@ export function handleReactNativeWebImport( } else { statements.push( t.importDeclaration( - [t.importSpecifier(specifier.local, specifier.local)], + [t.importSpecifier(specifier.local, t.identifier(name))], t.stringLiteral(`react-native-css/components/${name}`), ), ); @@ -89,6 +91,10 @@ export function handleReactNativeWebImport( ), ); } else { + if (specifier.importKind && specifier.importKind !== "value") { + statements.push(t.importDeclaration([specifier], source)); + continue; + } const localName = t.isStringLiteral(specifier.imported) ? specifier.imported.value : specifier.imported.name; @@ -158,6 +164,28 @@ export function handleReactNativeWebIdentifierRequire( } } +export function handleReactNativeWebInteropRequireDefault( + path: NodePath, + t: BabelTypes, + id: string, + init: CallExpression, + source: string, + filename: string, +) { + const parsed = parseReactNativeWebSource(source, filename); + if (!parsed) return; + + const wrapped = t.cloneNode(init); + wrapped.arguments = [ + t.callExpression(t.identifier("require"), [t.stringLiteral(parsed.source)]), + ]; + return [ + t.variableDeclaration(path.node.kind, [ + t.variableDeclarator(t.identifier(id), wrapped), + ]), + ]; +} + export function handleReactNativeWebObjectPatternRequire( path: NodePath, t: BabelTypes, @@ -180,6 +208,7 @@ export function handleReactNativeWebObjectPatternRequire( // We need to exit as we do not handle `const { Text, ...rest } = require('react-native-web');` return; } else if ( + identifier.computed || !(t.isIdentifier(identifier.value) && t.isIdentifier(identifier.key)) ) { // Bail out on anything that isn't `const { : } = require('react-native-web');` diff --git a/src/babel/react-native.ts b/src/babel/react-native.ts index 2522a848..e035fec1 100644 --- a/src/babel/react-native.ts +++ b/src/babel/react-native.ts @@ -48,6 +48,7 @@ export function handleReactNativeImport( filename: string, ): Statement[] | undefined { const { specifiers, source } = declaration; + if (declaration.importKind && declaration.importKind !== "value") return; const rnwSource = parseReactNativeSource(source.value, filename); if (!rnwSource) { @@ -76,7 +77,7 @@ export function handleReactNativeImport( } else { statements.push( t.importDeclaration( - [t.importSpecifier(specifier.local, specifier.local)], + [t.importSpecifier(specifier.local, t.identifier(name))], t.stringLiteral(`react-native-css/components/${name}`), ), ); @@ -89,6 +90,10 @@ export function handleReactNativeImport( ), ); } else { + if (specifier.importKind && specifier.importKind !== "value") { + statements.push(t.importDeclaration([specifier], source)); + continue; + } const localName = t.isStringLiteral(specifier.imported) ? specifier.imported.value : specifier.imported.name; @@ -180,6 +185,7 @@ export function handleReactNativeObjectPatternRequire( // We need to exit as we do not handle `const { Text, ...rest } = require('react-native');` return; } else if ( + identifier.computed || !(t.isIdentifier(identifier.value) && t.isIdentifier(identifier.key)) ) { // Bail out on anything that isn't `const { : } = require('react-native');` diff --git a/src/compiler/atRules.ts b/src/compiler/atRules.ts index d1755589..6943f672 100644 --- a/src/compiler/atRules.ts +++ b/src/compiler/atRules.ts @@ -1,4 +1,5 @@ import type { + Declaration, DeclarationBlock, ParsedComponent, Rule, @@ -99,6 +100,49 @@ export function parsePropAtRule(rules?: (Rule | PropAtRule)[]) { return mapping; } +const mappingProperty = "-rn-native-mapping"; + +export function isMappingProperty(name: string) { + return name === mappingProperty || name.startsWith(`${mappingProperty}-`); +} + +/** Declaration metadata survives the optimizer flattening nested at rules. */ +export function parsePropDeclarations(declarations: Declaration[] = []) { + const mapping: StyleRuleMapping = {}; + for (const declaration of declarations) { + if ( + declaration.property !== "custom" || + !isMappingProperty(declaration.value.name) + ) { + continue; + } + const { name, value } = declaration.value; + const target = value.filter( + (item): item is Extract => + item.type === "token" && item.value.type !== "white-space", + ); + if (target.length === 0) continue; + nativeMappingAtRuleBlock( + [ + { + type: "token", + value: { + type: "ident", + value: + name === mappingProperty + ? "*" + : name.slice(mappingProperty.length + 1), + }, + }, + { type: "token", value: { type: "colon" } }, + ...target, + ], + mapping, + ); + } + return mapping; +} + function nativeMappingAtRuleBlock( token: Extract[], mapping: StyleRuleMapping = {}, diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 214cd615..363ded58 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -4,7 +4,6 @@ import { inspect } from "node:util"; import { debug } from "debug"; import { type ContainerRule, - type MediaQuery as CSSMediaQuery, type CustomAtRules, type MediaRule, type ParsedComponent, @@ -13,7 +12,11 @@ import { type Visitor, } from "lightningcss"; -import { maybeMutateReactNativeOptions, parsePropAtRule } from "./atRules"; +import { + maybeMutateReactNativeOptions, + parsePropAtRule, + parsePropDeclarations, +} from "./atRules"; import type { CompilerOptions, ContainerQuery, @@ -95,7 +98,38 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) { options.inlineRem = effectiveRem; } - const firstPassVisitor: Visitor = {}; + const firstPassVisitor: Visitor = { + Rule(rule) { + if ( + rule.type === "unknown" && + ["cssInterop", "react-native"].includes(rule.value.name) + ) { + throw new Error( + `Unsupported @${rule.value.name} configuration. Use the compiler inlineVariables.exclude option to preserve variables, @media (prefers-color-scheme: dark) for dark mode, and React Native Appearance.setColorScheme() for manual selection.`, + ); + } + // Reject before variable inlining can erase an unsupported condition and + // accidentally turn its values into unconditional declarations. + if ( + rule.type === "style" && + rule.value.selectors.some( + (selector) => + selector.some( + (part) => part.type === "pseudo-class" && part.kind === "root", + ) && + selector.some( + (part) => + part.type === "class" || + (part.type === "attribute" && part.name === "class"), + ), + ) + ) { + throw new Error( + "Class-qualified :root selectors are unsupported on native. Use @media (prefers-color-scheme: dark) for dark mode, and React Native Appearance.setColorScheme() for manual selection.", + ); + } + }, + }; if (effectiveRem !== false) { const remMultiplier = effectiveRem; @@ -255,7 +289,12 @@ function extractRule( const value = rule.value; const declarationBlock = value.declarations; - mapping = { ...mapping, ...parsePropAtRule(value.rules) }; + mapping = { + ...mapping, + ...parsePropAtRule(value.rules), + ...parsePropDeclarations(declarationBlock?.declarations), + ...parsePropDeclarations(declarationBlock?.importantDeclarations), + }; // If the rule is a style declaration, extract it with the `getExtractedStyle` function and store it in the `declarations` map builder = builder.fork("style", value.selectors); @@ -338,39 +377,16 @@ function extractMedia( builder: StylesheetBuilder, mapping: StyleRuleMapping, ) { - builder = builder.fork("media"); - - // Initialize an empty array to store screen media queries - const media: CSSMediaQuery[] = []; - - // Iterate over all media queries in the mediaRule + // Comma separated queries are alternatives. Each branch inherits the outer + // conditions without adding its siblings as required conjunctions. for (const mediaQuery of mediaRule.query.mediaQueries) { - if ( - // If this is only a media query - (mediaQuery.mediaType === "print" && mediaQuery.qualifier !== "not") || - // If this is a @media not print {} - // We can only do this if there are no conditions, as @media not print and (min-width: 100px) could be valid - (mediaQuery.mediaType !== "print" && - mediaQuery.qualifier === "not" && - mediaQuery.condition === null) - ) { + const queryBuilder = builder.fork("media"); + if (!parseMediaQuery(mediaQuery, queryBuilder)) { continue; } - - media.push(mediaQuery); - } - - if (media.length === 0) { - return; - } - - for (const m of media) { - parseMediaQuery(m, builder); - } - - // Iterate over all rules in the mediaRule and extract their styles using the updated CompilerCollection - for (const rule of mediaRule.rules) { - extractRule(rule, builder, mapping); + for (const rule of mediaRule.rules) { + extractRule(rule, queryBuilder, mapping); + } } } @@ -387,9 +403,12 @@ function extractContainer( builder = builder.fork("container"); // Iterate over all rules inside the containerRule and extract their styles using the updated CompilerCollection - const query: ContainerQuery = { - m: parseContainerCondition(containerRule.condition, builder), - }; + const condition = parseContainerCondition(containerRule.condition, builder); + if (!condition) { + // An unsupported condition must not turn into an unconditional container. + return; + } + const query: ContainerQuery = { m: condition }; if (containerRule.name) { query.n = `c:${containerRule.name}`; @@ -439,21 +458,14 @@ function parsePropertyInitialValue( case "length-percentage": return parseLength(component.value, builder); case "token-list": - return reduceParseUnparsed( - component.value, - builder, - "@property", - false, - ); + return reduceParseUnparsed(component.value, builder, "@property", false); case "custom-ident": case "literal": return component.value; case "repeated": { const results = component.value.components .map((c) => parsePropertyInitialValue(c, builder)) - .filter( - (v): v is NonNullable => v !== undefined, - ); + .filter((v): v is NonNullable => v !== undefined); // Unwrap single-child repeated values so downstream consumers get a // scalar instead of a 1-element array. For example, `+` with // initial-value `10px` should produce the same shape as ``. diff --git a/src/compiler/container-query.ts b/src/compiler/container-query.ts index 32c25861..4d525493 100644 --- a/src/compiler/container-query.ts +++ b/src/compiler/container-query.ts @@ -36,9 +36,14 @@ function parseContainerQueryCondition( const query = parseContainerCondition(condition.value, builder); return query ? ["!", query] : undefined; case "operation": - const conditions = condition.conditions - .map((c) => parseContainerQueryCondition(c, builder)) - .filter((v): v is MediaCondition => !!v); + const parsed = condition.conditions.map((c) => + parseContainerCondition(c, builder), + ); + if (condition.operator === "and" && parsed.some((c) => !c)) { + // Dropping a conjunct would broaden the query to unrelated containers. + return; + } + const conditions = parsed.filter((c): c is MediaCondition => !!c); if (conditions.length === 0) { return; diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts index 13013642..9ca2aab8 100644 --- a/src/compiler/declarations.ts +++ b/src/compiler/declarations.ts @@ -37,6 +37,7 @@ import type { } from "lightningcss"; import { isStyleFunction } from "../utilities"; +import { isMappingProperty } from "./atRules"; import type { StyleDescriptor, StyleFunction, @@ -62,13 +63,37 @@ type Parser = ( const propertyRename: Record = { "background-image": "experimental_backgroundImage", + // React Native has no border-inline-* props, but ships the equivalent + // RTL-aware border-start-* / border-end-* props + "border-inline-end-color": "border-end-color", + "border-inline-end-width": "border-end-width", + "border-inline-start-color": "border-start-color", + "border-inline-start-width": "border-start-width", + "border-block-start-width": "border-top-width", + "border-block-end-width": "border-bottom-width", "font-variant-caps": "font-variant", }; +// React Native only supports a uniform borderStyle, so per-side border +// styles have no native equivalent and are dropped. "solid" is dropped +// silently as it matches React Native's default rendering. +const unsupportedLogicalStyles = new Set([ + "border-inline-style", + "border-inline-start-style", + "border-inline-end-style", + "border-block-style", + "border-block-start-style", + "border-block-end-style", +]); + const unparsedRuntimeParsing = new Set([ "animation", + "animation-name", "border", + "border-block-width", + "border-inline-width", "box-shadow", + "filter", "line-height", "rotate", "scale", @@ -102,10 +127,11 @@ const parsers: { "border-block-color": parseBorderColor, "border-block-end": parseBorderBlockEnd, "border-block-end-color": parseColorDeclaration, + "border-block-end-style": parseBorderInlineStyle, "border-block-end-width": parseBorderSideWidthDeclaration, "border-block-start": parseBorderBlockStart, "border-block-start-color": parseColorDeclaration, - "border-block-start-style": parseBorderStyleDeclaration, + "border-block-start-style": parseBorderInlineStyle, "border-block-start-width": parseBorderSideWidthDeclaration, "border-block-style": parseBorderBlockStyle, "border-block-width": parseBorderBlockWidth, @@ -321,11 +347,11 @@ function parseInsetInline( builder: StylesheetBuilder, ) { builder.addShorthand("inset-inline", { - "inset-block-start": parseLengthPercentageOrAuto( + "inset-inline-start": parseLengthPercentageOrAuto( value.inlineStart, builder, ), - "inset-block-end": parseLengthPercentageOrAuto(value.inlineEnd, builder), + "inset-inline-end": parseLengthPercentageOrAuto(value.inlineEnd, builder), }); } @@ -382,14 +408,14 @@ function parseBorderColor( const start = parseColor(declaration.value.start, builder); const end = parseColor(declaration.value.end, builder); - if (start === end) { + if (declaration.property === "border-inline-color") { + builder.addDescriptor("border-start-color", start); + builder.addDescriptor("border-end-color", end); + } else if (start === end) { builder.addDescriptor(declaration.property, start); - } else if (declaration.property === "border-block-color") { + } else { builder.addDescriptor("border-top-color", start); builder.addDescriptor("border-bottom-color", end); - } else { - builder.addDescriptor("border-left-color", start); - builder.addDescriptor("border-right-color", end); } } } @@ -438,13 +464,13 @@ function parseBorderBlock( builder: StylesheetBuilder, ) { builder.addDescriptor("border-block-color", parseColor(value.color, builder)); - builder.addDescriptor( - "border-block-width", - parseBorderSideWidth(value.width, builder), - ); - builder.addDescriptor( - "border-block-style", + const width = parseBorderSideWidth(value.width, builder); + builder.addDescriptor("border-top-width", width); + builder.addDescriptor("border-bottom-width", width); + dropUnsupportedInlineStyle( parseBorderStyle(value.style, builder), + builder, + "border-block-style", ); } @@ -457,9 +483,14 @@ function parseBorderBlockStart( parseColor(value.color, builder), ); builder.addDescriptor( - "border-block-start-width", + "border-top-width", parseBorderSideWidth(value.width, builder), ); + dropUnsupportedInlineStyle( + parseBorderStyle(value.style, builder), + builder, + "border-block-start-style", + ); } function parseBorderBlockEnd( @@ -471,26 +502,31 @@ function parseBorderBlockEnd( parseColor(value.color, builder), ); builder.addDescriptor( - "border-block-end-width", + "border-bottom-width", parseBorderSideWidth(value.width, builder), ); + dropUnsupportedInlineStyle( + parseBorderStyle(value.style, builder), + builder, + "border-block-end-style", + ); } function parseBorderInline( { value }: DeclarationType<"border-inline">, builder: StylesheetBuilder, ) { - builder.addDescriptor( - "border-inline-color", - parseColor(value.color, builder), - ); - builder.addDescriptor( - "border-inline-width", - parseBorderSideWidth(value.width, builder), - ); - builder.addDescriptor( - "border-inline-style", + const color = parseColor(value.color, builder); + const width = parseBorderSideWidth(value.width, builder); + + builder.addDescriptor("border-start-color", color); + builder.addDescriptor("border-end-color", color); + builder.addDescriptor("border-start-width", width); + builder.addDescriptor("border-end-width", width); + dropUnsupportedInlineStyle( parseBorderStyle(value.style, builder), + builder, + "border-inline-style", ); } @@ -498,17 +534,15 @@ function parseBorderInlineStart( { value }: DeclarationType<"border-inline-start">, builder: StylesheetBuilder, ) { + builder.addDescriptor("border-start-color", parseColor(value.color, builder)); builder.addDescriptor( - "border-inline-start-color", - parseColor(value.color, builder), - ); - builder.addDescriptor( - "border-inline-start-width", + "border-start-width", parseBorderSideWidth(value.width, builder), ); - builder.addDescriptor( - "border-inline-start-style", + dropUnsupportedInlineStyle( parseBorderStyle(value.style, builder), + builder, + "border-inline-start-style", ); } @@ -516,17 +550,15 @@ function parseBorderInlineEnd( { value }: DeclarationType<"border-inline-end">, builder: StylesheetBuilder, ) { + builder.addDescriptor("border-end-color", parseColor(value.color, builder)); builder.addDescriptor( - "border-inline-end-color", - parseColor(value.color, builder), - ); - builder.addDescriptor( - "border-inline-end-width", + "border-end-width", parseBorderSideWidth(value.width, builder), ); - builder.addDescriptor( - "border-inline-end-style", + dropUnsupportedInlineStyle( parseBorderStyle(value.style, builder), + builder, + "border-inline-end-style", ); } @@ -535,9 +567,13 @@ export function parseBorderInlineWidth( builder: StylesheetBuilder, ) { builder.addDescriptor( - "border-inline-width", + "border-start-width", parseBorderSideWidth(declaration.value.start, builder), ); + builder.addDescriptor( + "border-end-width", + parseBorderSideWidth(declaration.value.end, builder), + ); } export function parseBorderInlineStyle( @@ -545,31 +581,41 @@ export function parseBorderInlineStyle( | "border-inline-style" | "border-inline-start-style" | "border-inline-end-style" + | "border-block-start-style" + | "border-block-end-style" >, builder: StylesheetBuilder, ) { if (typeof declaration.value === "string") { - builder.addDescriptor( - declaration.property, + dropUnsupportedInlineStyle( parseBorderStyle(declaration.value, builder), - ); - } else if (declaration.value.start === declaration.value.end) { - builder.addDescriptor( + builder, declaration.property, - parseBorderStyle(declaration.value.start, builder), ); } else { - builder.addDescriptor( - "border-inline-start-style", + dropUnsupportedInlineStyle( parseBorderStyle(declaration.value.start, builder), + builder, + "border-inline-start-style", ); - builder.addDescriptor( - "border-inline-end-style", + dropUnsupportedInlineStyle( parseBorderStyle(declaration.value.end, builder), + builder, + "border-inline-end-style", ); } } +function dropUnsupportedInlineStyle( + style: string | undefined, + builder: StylesheetBuilder, + property: string, +) { + if (style !== undefined && style !== "solid") { + builder.addWarning("style", property, style); + } +} + function parseFlexFlow( { value }: DeclarationType<"flex-flow">, builder: StylesheetBuilder, @@ -839,7 +885,7 @@ export function parseScaleValue( builder: StylesheetBuilder, ): StyleDescriptor { if (translate === "none") { - return 0; + return 1; } return parseLength(translate[prop], builder); @@ -917,6 +963,11 @@ export function parseUnparsedDeclaration( return; } + if (unsupportedLogicalStyles.has(property)) { + builder.addWarning("property", property); + return; + } + builder.setWarningProperty(property); /** @@ -967,7 +1018,10 @@ export function parseCustomDeclaration( ) { const property = declaration.value.name; - if (property === "-webkit-line-clamp") { + if (isMappingProperty(property)) { + // Consumed before declarations so property order cannot affect mapping. + return; + } else if (property === "-webkit-line-clamp") { builder.addDescriptor( property, parseUnparsed(declaration.value.value, builder, property), @@ -1607,7 +1661,7 @@ export function parseColorDeclaration( builder: StylesheetBuilder, ) { builder.addDescriptor( - declaration.property, + propertyRename[declaration.property] ?? declaration.property, parseColor(declaration.value, builder), ); } @@ -2163,12 +2217,8 @@ export function parseBorderBlockWidth( const start = parseBorderSideWidth(declaration.value.start, builder); const end = parseBorderSideWidth(declaration.value.end, builder); - if (start === end) { - builder.addDescriptor("border-block-width", start); - } else { - builder.addDescriptor("border-block-start-width", start); - builder.addDescriptor("border-block-end-width", end); - } + builder.addDescriptor("border-top-width", start); + builder.addDescriptor("border-bottom-width", end); } function parseBorderBlockStyle( @@ -2178,12 +2228,8 @@ function parseBorderBlockStyle( const start = parseBorderStyle(declaration.value.start, builder); const end = parseBorderStyle(declaration.value.end, builder); - if (start == end) { - builder.addDescriptor("border-block-style", start); - } else { - builder.addDescriptor("border-block-start-style", start); - builder.addDescriptor("border-block-end-style", end); - } + dropUnsupportedInlineStyle(start, builder, "border-block-start-style"); + dropUnsupportedInlineStyle(end, builder, "border-block-end-style"); } export function parseBorderSideWidthDeclaration( @@ -2191,7 +2237,7 @@ export function parseBorderSideWidthDeclaration( builder: StylesheetBuilder, ) { builder.addDescriptor( - declaration.property, + propertyRename[declaration.property] ?? declaration.property, parseBorderSideWidth(declaration.value, builder), ); } @@ -2235,12 +2281,10 @@ export function parseLineHeightDeclaration( declaration: DeclarationType<"line-height">, builder: StylesheetBuilder, ) { - builder.addDescriptor("line-height", [ - {}, - "lineHeight", - [parseLineHeight(declaration.value, builder)], - 1, - ]); + builder.addDescriptor( + "line-height", + parseLineHeight(declaration.value, builder), + ); } export function parseLineHeight( @@ -2251,7 +2295,7 @@ export function parseLineHeight( case "normal": return undefined; case "number": - return [{}, "em", [value.value], 1]; + return [{}, "em", value.value, 1]; case "length": { const length = value.value; @@ -2681,7 +2725,12 @@ export function parseColorMix( builder: StylesheetBuilder, property: string, ): StyleDescriptor { - const [inToken, whitespace, colorSpace, comma, ...rest] = tokens; + const [inToken, whitespace, colorSpace, comma, ...remaining] = tokens; + const rest = remaining.filter( + (token) => + token.type !== "token" || + !["white-space", "comment"].includes(token.value.type), + ); if ( typeof inToken !== "object" || inToken.type !== "token" || @@ -2740,11 +2789,6 @@ export function parseColorMix( const rightColorArg = parseUnparsed(nextToken, builder, property); - if (rightColorArg === "transparent") { - // Ignore the rest, treat as single color with alpha - return [{}, "colorMix", [colorSpaceArg, leftColorArg, leftColorPercentage]]; - } - nextToken = rest.shift(); let rightColorPercentage: StyleDescriptor | undefined; if (nextToken?.type !== "token" || nextToken.value.type !== "comma") { @@ -3144,21 +3188,25 @@ function parseBackgroundImage( declaration: DeclarationType<"background-image">, builder: StylesheetBuilder, ) { - builder.addDescriptor( - "experimental_backgroundImage", - declaration.value.flatMap((image): StyleDescriptor[] => { - switch (image.type) { - case "gradient": { - const gradient = parseGradient(image.value, builder); - return gradient ? [gradient] : []; + builder.addDescriptor("experimental_backgroundImage", [ + {}, + "join", + [ + declaration.value.flatMap((image): StyleDescriptor[] => { + switch (image.type) { + case "gradient": { + const gradient = parseGradient(image.value, builder); + return gradient ? [gradient] : []; + } + case "none": + return ["none"]; } - case "none": - return ["none"]; - } - return []; - }), - ); + return []; + }), + ", ", + ], + ]); return; } @@ -3213,10 +3261,10 @@ function parseGradientItem( args.push(parseLength(item.position, builder)); } - return [{}, "@colorStop", args, 1]; + return [{}, "colorStop", args, 1]; } case "hint": - return parseLength(item.value, builder); + return [{}, "gradientPosition", parseLength(item.value, builder), 1]; } } @@ -3316,7 +3364,7 @@ function parseFilter( } as unknown as StyleDescriptor; case "hue-rotate": return { - [value.type]: parseAngle(value.value, builder), + [toRNProperty(value.type)]: parseAngle(value.value, builder), } as unknown as StyleDescriptor; case "drop-shadow": return [ diff --git a/src/compiler/lightningcss-loader.ts b/src/compiler/lightningcss-loader.ts index 5565e74d..5acf64fb 100644 --- a/src/compiler/lightningcss-loader.ts +++ b/src/compiler/lightningcss-loader.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/no-require-imports */ +import { dirname, resolve } from "node:path"; + export function lightningcssLoader() { let lightningcssPath: string | undefined; @@ -33,23 +35,22 @@ export function lightningcssLoader() { lightningcssPath, ) as typeof import("lightningcss"); + let version: unknown; try { - const lightningcssPackageJSONPath = require.resolve("../../package.json", { - paths: [lightningcssPath], - }); - - const packageJSON = require(lightningcssPackageJSONPath) as Record< - string, - unknown - >; - - if (packageJSON.version === "1.30.2") { - throw new Error( - "[react-native-css] lightningcss version 1.30.2 has a critical bug that breaks compilation. Please pin the version of lightningcss to 1.30.1; or try upgrading.", - ); - } + // Lightning CSS keeps its entry in node/ and does not export package.json. + // Resolve relative to the selected copy, not the consumer or this loader. + const packageJSON = require( + resolve(dirname(lightningcssPath), "../package.json"), + ) as Record; + version = packageJSON.version; } catch { - // Intentionally left empty + // Some bundlers do not retain package metadata. Loading remains supported. + } + + if (version === "1.30.2") { + throw new Error( + "[react-native-css] lightningcss version 1.30.2 has a critical bug that breaks compilation. Please pin the version of lightningcss to 1.30.1; or try upgrading.", + ); } return { diff --git a/src/compiler/media-query.ts b/src/compiler/media-query.ts index c8733c12..1408adc0 100644 --- a/src/compiler/media-query.ts +++ b/src/compiler/media-query.ts @@ -24,9 +24,10 @@ export function parseMediaQuery( let condition: MediaCondition | undefined; if (query.mediaType) { - // Print is for printing documents + // Native screens never match print. Negating that media type matches + // regardless of the remaining feature tests in the same conjunction. if (query.mediaType === "print") { - return; + return query.qualifier === "not"; } // These all/screen are not conditions, they always apply @@ -40,7 +41,7 @@ export function parseMediaQuery( // If any of these are undefined, the media query is invalid if (!condition || condition.some((v) => v === undefined)) { - return; + return false; } } @@ -50,7 +51,8 @@ export function parseMediaQuery( : platformCondition || condition; if (!mediaQuery) { - return; + // Unqualified all/screen apply; not all and not screen do not. + return query.qualifier !== "not"; } if (query.qualifier === "not") { @@ -58,6 +60,7 @@ export function parseMediaQuery( } builder.addMediaQuery(mediaQuery); + return true; } function parseMediaQueryCondition( @@ -65,15 +68,23 @@ function parseMediaQueryCondition( builder: StylesheetBuilder, ): MediaCondition | undefined { switch (query.type) { - case "feature": - return parseFeature(query.value, builder); + case "feature": { + const feature = parseFeature(query.value, builder); + return feature?.some((value) => value === undefined) + ? undefined + : feature; + } case "not": const mediaQuery = parseMediaQueryCondition(query.value, builder); return mediaQuery ? ["!", mediaQuery] : undefined; case "operation": - const mediaQueries = query.conditions - .map((c) => parseMediaQueryCondition(c, builder)) - .filter((v): v is MediaCondition => !!v); + const parsed = query.conditions.map((c) => + parseMediaQueryCondition(c, builder), + ); + if (query.operator === "and" && parsed.some((c) => !c)) { + return; + } + const mediaQueries = parsed.filter((v): v is MediaCondition => !!v); if (mediaQueries.length === 0) { return; diff --git a/src/compiler/selector-builder.ts b/src/compiler/selector-builder.ts index 88561b78..6787c0ca 100644 --- a/src/compiler/selector-builder.ts +++ b/src/compiler/selector-builder.ts @@ -260,7 +260,12 @@ function parseComponents( ? // [data-*] are turned into `dataSet` queries ["d", toRNProperty(component.name.replace("data-", ""))] : // Everything else is turned into `attribute` queries - ["a", toRNProperty(component.name)]; + [ + "a", + component.name.startsWith("aria-") + ? component.name + : toRNProperty(component.name), + ]; if (component.operation) { let operator: AttrSelectorOperator | undefined; switch (component.operation.operator) { @@ -289,6 +294,15 @@ function parseComponents( if (operator) { // Append the operator onto the attribute query attributeQuery.push(operator, component.operation.value); + if ( + component.operation.caseSensitivity === "ascii-case-insensitive" + ) { + attributeQuery.push("i"); + } else if ( + component.operation.caseSensitivity === "explicit-case-sensitive" + ) { + attributeQuery.push("s"); + } } } getAttributeQuery(ref).push(attributeQuery); @@ -311,7 +325,7 @@ function parseComponents( getAttributeQuery(ref).unshift([ "a", "className", - "*=", + "~=", component.name, ]); } else { @@ -464,11 +478,23 @@ function parseIsWhereComponents( ? // [data-*] are turned into `dataSet` queries ["d", toRNProperty(component.name.replace("data-", ""))] : // Everything else is turned into `attribute` queries - ["a", toRNProperty(component.name)]; + [ + "a", + component.name.startsWith("aria-") + ? component.name + : toRNProperty(component.name), + ]; if (component.operation) { const operator = operatorMap[component.operation.operator]; // Append the operator onto the attribute query attributeQuery.push(operator, component.operation.value); + if (component.operation.caseSensitivity === "ascii-case-insensitive") { + attributeQuery.push("i"); + } else if ( + component.operation.caseSensitivity === "explicit-case-sensitive" + ) { + attributeQuery.push("s"); + } } queries ??= [{ specificity: [] }]; for (const query of queries) { diff --git a/src/components/ImageBackground.tsx b/src/components/ImageBackground.tsx index 954482aa..9dae55a2 100644 --- a/src/components/ImageBackground.tsx +++ b/src/components/ImageBackground.tsx @@ -12,12 +12,8 @@ import { import { copyComponentProperties } from "./copyComponentProperties"; const mapping: StyledConfiguration = { - className: { - target: "style", - nativeStyleMapping: { - backgroundColor: true, - }, - }, + className: "style", + imageClassName: "imageStyle", }; export const ImageBackground = copyComponentProperties( diff --git a/src/components/KeyboardAvoidingView.tsx b/src/components/KeyboardAvoidingView.tsx index 75f01d8f..8c89e612 100644 --- a/src/components/KeyboardAvoidingView.tsx +++ b/src/components/KeyboardAvoidingView.tsx @@ -12,9 +12,8 @@ import { import { copyComponentProperties } from "./copyComponentProperties"; const mapping: StyledConfiguration = { - className: { - target: "style", - }, + className: "style", + contentContainerClassName: "contentContainerStyle", }; export const KeyboardAvoidingView = copyComponentProperties( diff --git a/src/components/index.cts b/src/components/index.cts index d8aeb204..ead6ff4e 100644 --- a/src/components/index.cts +++ b/src/components/index.cts @@ -90,7 +90,7 @@ module.exports = { return require("react-native").TouchableNativeFeedback; }, get TouchableWithoutFeedback() { - return require("react-native").TouchableWithoutFeedback; + return require("./TouchableWithoutFeedback").TouchableWithoutFeedback; }, get VirtualizedSectionList() { return require("react-native").VirtualizedSectionList; @@ -260,4 +260,49 @@ module.exports = { get VirtualViewMode() { return require("react-native").VirtualViewMode; }, + get EventEmitter() { + return require("react-native").EventEmitter; + }, + get unstable_NativeText() { + return require("react-native").unstable_NativeText; + }, + get unstable_NativeView() { + return require("react-native").unstable_NativeView; + }, + get unstable_VirtualArray() { + return require("react-native").unstable_VirtualArray; + }, + get unstable_createVirtualCollectionView() { + return require("react-native").unstable_createVirtualCollectionView; + }, + get unstable_VirtualColumn() { + return require("react-native").unstable_VirtualColumn; + }, + get unstable_VirtualColumnGenerator() { + return require("react-native").unstable_VirtualColumnGenerator; + }, + get unstable_VirtualRow() { + return require("react-native").unstable_VirtualRow; + }, + get unstable_getScrollParent() { + return require("react-native").unstable_getScrollParent; + }, + get unstable_DEFAULT_INITIAL_NUM_TO_RENDER() { + return require("react-native").unstable_DEFAULT_INITIAL_NUM_TO_RENDER; + }, + get NativeComponentRegistry() { + return require("react-native").NativeComponentRegistry; + }, + get ReactNativeVersion() { + return require("react-native").ReactNativeVersion; + }, + get useAnimatedValueXY() { + return require("react-native").useAnimatedValueXY; + }, + get useAnimatedColor() { + return require("react-native").useAnimatedColor; + }, + get usePressability() { + return require("react-native").usePressability; + }, }; diff --git a/src/jest/index.ts b/src/jest/index.ts index cc125390..a8d249f5 100644 --- a/src/jest/index.ts +++ b/src/jest/index.ts @@ -21,7 +21,7 @@ export const testID = "react-native-css"; beforeEach(() => { StyleCollection.styles.clear(); dimensions.set(Dimensions.get("window")); - Appearance.setColorScheme(null); + Appearance.setColorScheme("unspecified"); colorScheme.set(null); }); diff --git a/src/metro/cache-version.ts b/src/metro/cache-version.ts new file mode 100644 index 00000000..38ba3f30 --- /dev/null +++ b/src/metro/cache-version.ts @@ -0,0 +1,48 @@ +import { createHash } from "node:crypto"; +import { readdirSync, readFileSync } from "node:fs"; +import { dirname, join, relative } from "node:path"; + +import type { CompilerOptions } from "../compiler"; + +/** + * Expo's supervising worker bypasses a custom worker's getCacheKey hook. + * Put the engine fingerprint in Metro's top level cacheVersion instead so + * both supervised and direct workers invalidate compiled CSS after an update. + */ +export function getCacheVersion( + cacheVersion: string | undefined, + options: CompilerOptions | undefined, +) { + const root = dirname(__dirname); + const hash = createHash("sha256"); + + function visit(directory: string) { + for (const entry of readdirSync(directory, { withFileTypes: true }).sort( + (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0), + )) { + const file = join(directory, entry.name); + if (entry.isDirectory()) { + if (!["__tests__", "__fixtures__", "__mocks__"].includes(entry.name)) { + visit(file); + } + } else if ( + entry.isFile() && + /\.[cm]?[jt]sx?$/.test(entry.name) && + !/\.d\.[cm]?ts$/.test(entry.name) && + !/\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name) + ) { + // Relative names keep identical installations independent of location. + hash.update(relative(root, file).split("\\").join("/")); + hash.update("\0"); + hash.update(readFileSync(file)); + hash.update("\0"); + } + } + } + + // Cover transitive engine helpers as well as the transformer itself. Maps, + // declarations, and tests do not affect the generated application code. + visit(root); + hash.update(JSON.stringify(options ?? {})); + return `${cacheVersion ?? ""}:react-native-css:${hash.digest("hex")}`; +} diff --git a/src/metro/index.ts b/src/metro/index.ts index 73c041c7..63290e59 100644 --- a/src/metro/index.ts +++ b/src/metro/index.ts @@ -4,6 +4,7 @@ import { versions } from "node:process"; import type { MetroConfig } from "metro-config"; import { type CompilerOptions } from "../compiler"; +import { getCacheVersion } from "./cache-version"; import { nativeResolver, webResolver } from "./resolver"; import { setupTypeScript } from "./typescript"; @@ -47,6 +48,7 @@ export function withReactNativeCSS< return { ...config, + cacheVersion: getCacheVersion(config.cacheVersion, options), transformerPath: require.resolve("./metro-transformer"), transformer: { ...config.transformer, diff --git a/src/metro/metro-transformer.ts b/src/metro/metro-transformer.ts index e56e1ca7..16eb4f53 100644 --- a/src/metro/metro-transformer.ts +++ b/src/metro/metro-transformer.ts @@ -2,12 +2,15 @@ import { unstable_transformerPath } from "@expo/metro-config"; import type { JsTransformerConfig, JsTransformOptions, - TransformResponse, } from "metro-transform-worker"; import { compile, type CompilerOptions } from "../compiler"; import { getNativeInjectionCode } from "./injection-code"; +type TransformResponse = Awaited< + ReturnType +>; + const worker = // eslint-disable-next-line @typescript-eslint/no-require-imports require(unstable_transformerPath) as typeof import("metro-transform-worker"); diff --git a/src/metro/typescript.ts b/src/metro/typescript.ts index 23f204d4..67f81b4a 100644 --- a/src/metro/typescript.ts +++ b/src/metro/typescript.ts @@ -1,5 +1,6 @@ /* eslint-disable */ import { existsSync, readFileSync, writeFileSync } from "fs"; +import { dirname, relative, resolve } from "node:path"; import { CommentArray, parse, stringify } from "comment-json"; @@ -16,11 +17,17 @@ export function setupTypeScript( } const configFileName = ts.findConfigFile( - "./", + resolve("."), ts.sys.fileExists, "tsconfig.json", ); + if (!configFileName) return; + + const configDirectory = dirname(resolve(configFileName)); + const environmentFile = resolve(envPath); + const relativeEnvironmentFile = relative(configDirectory, environmentFile); + const userConfig = parse( readFileSync(configFileName, { encoding: "utf-8", @@ -30,7 +37,8 @@ export function setupTypeScript( if ( typeof userConfig !== "object" || !userConfig || - Array.isArray(userConfig) + Array.isArray(userConfig) || + Object.prototype.toString.call(userConfig) !== "[object Object]" ) { return; } @@ -48,25 +56,64 @@ export function setupTypeScript( output.push(`Created ${cyan(envPath)}`); } - userConfig.include ??= new CommentArray(envPath); if ( Array.isArray(userConfig.include) && - !userConfig.include.includes(envPath) + !( + Array.isArray(userConfig.files) && + userConfig.files.includes(relativeEnvironmentFile) + ) && + !userConfig.include.includes(relativeEnvironmentFile) ) { - userConfig.include.push(envPath); + userConfig.include.push(relativeEnvironmentFile); updatedConfig = true; output.push( `Updated ${configFileName} to include the ${cyan(envPath)} file`, ); } + // Resolve inheritance on a copy so comments and the user's own settings survive. + const effectiveConfig = ts.parseJsonConfigFileContent( + JSON.parse(JSON.stringify(userConfig)), + ts.sys, + configDirectory, + {}, + resolve(configFileName), + ); + if ( + !effectiveConfig.fileNames.some( + (file: string) => resolve(file) === environmentFile, + ) + ) { + // A files entry can include the declaration even when inherited include or + // exclude settings omit it. Preserve the original implicit include as well. + if ( + effectiveConfig.raw.include === undefined && + effectiveConfig.raw.files === undefined + ) { + userConfig.include = new CommentArray("**/*"); + } + userConfig.files ??= new CommentArray( + ...(effectiveConfig.raw.files || []), + ); + if ( + Array.isArray(userConfig.files) && + !userConfig.files.includes(relativeEnvironmentFile) + ) { + userConfig.files.push(relativeEnvironmentFile); + updatedConfig = true; + output.push( + `Updated ${configFileName} to include the ${cyan(envPath)} file`, + ); + } + } + if (updatedConfig) { writeFileSync(configFileName, stringify(userConfig, null, 2)); } if (output.length) { console.log( - `${cyan(bold("NativeWind"))} made the following changes to your project to support TypeScript:\n - ${output.join("\n - ")}`, + `${cyan(bold("Nativewind"))} made the following changes to your project to support TypeScript:\n - ${output.join("\n - ")}`, ); } } catch {} diff --git a/src/native-internal/style-collection.ts b/src/native-internal/style-collection.ts index eff34009..21a296a6 100644 --- a/src/native-internal/style-collection.ts +++ b/src/native-internal/style-collection.ts @@ -91,7 +91,7 @@ globalThis.__react_native_css_style_collection ??= { if (options.vu) { for (const entry of options.vu) { - rootVariables(entry[0]).set(entry[1]); + universalVariables(entry[0]).set(entry[1]); } } diff --git a/src/native/api.tsx b/src/native/api.tsx index 3d68a3aa..6736f746 100644 --- a/src/native/api.tsx +++ b/src/native/api.tsx @@ -1,6 +1,6 @@ /* eslint-disable */ -import { useContext, useState, type ComponentType } from "react"; -import { Appearance } from "react-native"; +import { useContext, useEffect, useState, type ComponentType } from "react"; +import { Appearance, type ViewStyle } from "react-native"; import type { StyleDescriptor } from "react-native-css/compiler"; import { VariableContext } from "react-native-css/native-internal"; @@ -9,12 +9,14 @@ import type { ColorScheme, Props, ReactComponent, + Styled, StyledConfiguration, StyledOptions, } from "../runtime.types"; import { mappingToConfig, useNativeCss } from "./react/useNativeCss"; import { usePassthrough } from "./react/usePassthrough"; import { + cleanupEffect, colorScheme as colorSchemeObs, VAR_SYMBOL, type Effect, @@ -40,12 +42,9 @@ const defaultMapping: StyledConfiguration> = { * @param baseComponent * @param mapping */ -export const styled = < - const C extends ReactComponent, - const M extends StyledConfiguration, ->( - baseComponent: C, - mapping: M = defaultMapping as M, +export const styled: Styled = ( + baseComponent: ReactComponent, + mapping: StyledConfiguration = defaultMapping, options?: StyledOptions, ) => { let component: any; @@ -73,7 +72,7 @@ export const colorScheme: ColorScheme = { return colorSchemeObs.get() ?? Appearance.getColorScheme() ?? "light"; }, set(value) { - return colorSchemeObs.set(value); + return colorSchemeObs.set(value === "unspecified" ? null : value); }, }; @@ -97,28 +96,35 @@ export function useNativeVariable(name: string) { } const inheritedVariables = useContext(VariableContext); - const [effect, setState] = useState(() => { + const [, forceUpdate] = useState(0); + const [effect] = useState(() => { const effect: Effect = { observers: new Set(), - run: () => setState((state) => ({ ...state })), + run: () => forceUpdate((state) => state + 1), }; const get: Getter = (observable) => observable.get(effect); - return { ...effect, get }; + return Object.assign(effect, { get }); }); + useEffect(() => { + // React StrictMode replays setup after cleanup without another render. + if (effect.observers.size === 0) forceUpdate((state) => state + 1); + return () => cleanupEffect(effect); + }, [effect]); + cleanupEffect(effect); return resolveValue([{}, "var", [name]], effect.get, { inheritedVariables }); } /** * @deprecated Use `` instead. */ -export function vars(variables: Record) { +export function vars(variables: Record): ViewStyle { return Object.assign( { [VAR_SYMBOL]: "inline" }, Object.fromEntries( Object.entries(variables).map(([k, v]) => [k.replace(/^--/, ""), v]), ), - ); + ) as ViewStyle; } diff --git a/src/native/conditions/attributes.ts b/src/native/conditions/attributes.ts index 23b72804..e856e79e 100644 --- a/src/native/conditions/attributes.ts +++ b/src/native/conditions/attributes.ts @@ -11,7 +11,7 @@ export function testAttributes( } function testAttribute( - [type, prop, operator, testValue]: AttributeQuery, + [type, prop, operator, testValue, caseSensitivity]: AttributeQuery, props: Record | undefined | null, guards: RenderGuard[], ) { @@ -32,21 +32,38 @@ function testAttribute( return value !== undefined && value !== null && value !== false; } + if (operator === "!") return !value; + if (value === undefined || value === null || testValue === undefined) + return false; + + if ( + typeof value !== "string" && + typeof value !== "number" && + typeof value !== "boolean" + ) + return false; + let actual = String(value); + if (caseSensitivity === "i") { + // CSS attribute flags fold ASCII letters only, not Unicode characters. + actual = actual.replace(/[A-Z]/g, (letter) => letter.toLowerCase()); + testValue = testValue.replace(/[A-Z]/g, (letter) => letter.toLowerCase()); + } + switch (operator) { - case "!": - return !value; case "=": - return value == testValue; + return actual === testValue; case "~=": - return testValue && value?.toString().split(" ").includes(testValue); + return ( + testValue !== "" && actual.split(/[\t\n\f\r ]+/).includes(testValue) + ); case "|=": - return testValue && value?.toString().startsWith(testValue + "-"); + return actual === testValue || actual.startsWith(testValue + "-"); case "^=": - return testValue && value?.toString().startsWith(testValue); + return testValue !== "" && actual.startsWith(testValue); case "$=": - return testValue && value?.toString().endsWith(testValue); + return testValue !== "" && actual.endsWith(testValue); case "*=": - return testValue && value?.toString().includes(testValue); + return testValue !== "" && actual.includes(testValue); default: operator satisfies never; return false; diff --git a/src/native/conditions/container-query.ts b/src/native/conditions/container-query.ts index ac546c9a..e25df564 100644 --- a/src/native/conditions/container-query.ts +++ b/src/native/conditions/container-query.ts @@ -16,7 +16,7 @@ import { type ContainerContextValue, type Getter, } from "../reactivity"; -// import { testAttributes } from "./attributes"; +import { testAttributes } from "./attributes"; import type { RenderGuard } from "./guards"; export const DEFAULT_CONTAINER_NAME = "c:___default___"; @@ -47,15 +47,17 @@ export function testContainerQuery( return false; } - // if (query.a && !testAttributes(query.a, container.props, guards)) { - // return false; - // } + // Ancestor snapshots have their own context guard. Their attributes must not + // be compared with this descendant's props by the ordinary attribute guards. + if (query.a && !testAttributes(query.a, container.props, [])) { + return false; + } - if (query.m && !testContainerMediaCondition(query.m, container, get)) { + if (query.m && !testContainerMediaCondition(query.m, container.key, get)) { return false; } - if (query.p && !testContainerPseudoCondition(query.p, container, get)) { + if (query.p && !testContainerPseudoCondition(query.p, container.key, get)) { return false; } @@ -119,11 +121,11 @@ function testContainerMediaCondition( case ">": return left > right; case ">=": - return left > right; + return left >= right; case "<": - return left > right; + return left < right; case "<=": - return left > right; + return left <= right; default: condition[0] satisfies never; return false; diff --git a/src/native/conditions/media-query.ts b/src/native/conditions/media-query.ts index 75cd9006..afd51250 100644 --- a/src/native/conditions/media-query.ts +++ b/src/native/conditions/media-query.ts @@ -39,7 +39,7 @@ function testComparison(mediaQuery: MediaCondition, get: Getter): Boolean { switch (mediaQuery[1]) { case "dir": - return (I18nManager.isRTL && value === "rtl") || value === "ltr"; + return value === (I18nManager.isRTL ? "rtl" : "ltr"); case "hover": return true; case "platform": diff --git a/src/native/react/interaction.ts b/src/native/react/interaction.ts index f6e1ad3c..d412b15e 100644 --- a/src/native/react/interaction.ts +++ b/src/native/react/interaction.ts @@ -11,7 +11,7 @@ import { const mainCache = new WeakMap< WeakKey, - WeakMap void> + Map void>> >(); type Handler = (event: unknown) => void; @@ -40,14 +40,19 @@ const defaultHandlers: Record = { export function getInteractionHandler( weakKey: WeakKey, type: InteractionType, - handler = defaultHandlers[type], + handler?: Handler | null, ) { - let cache = mainCache.get(weakKey); - if (!cache) { - cache = new WeakMap(); - mainCache.set(weakKey, cache); + handler ??= defaultHandlers[type]; + let interactions = mainCache.get(weakKey); + if (!interactions) { + interactions = new Map(); + mainCache.set(weakKey, interactions); } + // A caller may reuse one callback for events that update opposite states. + let cache = interactions.get(type); + if (!cache) interactions.set(type, (cache = new WeakMap())); + let cached = cache.get(handler); if (!cached) { cached = (event: any) => { diff --git a/src/native/react/rules.ts b/src/native/react/rules.ts index f85a66f9..e0fe8d1a 100644 --- a/src/native/react/rules.ts +++ b/src/native/react/rules.ts @@ -8,6 +8,7 @@ import type { RenderGuard } from "../conditions/guards"; import { getDeepPath } from "../objects"; import { activeFamily, + cleanupEffect, containerLayoutFamily, focusFamily, hoverFamily, @@ -30,6 +31,7 @@ export function updateRules( forceUpdate = false, isRerender = true, ): ComponentState { + cleanupEffect(state.ruleEffect); const guards: RenderGuard[] = []; const rules = new Set(); if (forceUpdate) { @@ -144,13 +146,19 @@ export function updateRules( containers = { ...inheritedContainers, // This container becomes the default container - [DEFAULT_CONTAINER_NAME]: state.ruleEffectGetter, + [DEFAULT_CONTAINER_NAME]: { + key: state.ruleEffectGetter, + props: currentProps, + }, }; } // This this component as the named container for (const name of rule.c) { - containers![name] = state.ruleEffectGetter; + containers![name] = { + key: state.ruleEffectGetter, + props: currentProps, + }; } // Enable hover/active/focus/layout handlers @@ -212,14 +220,14 @@ export function updateRules( }; } - if (usesVariables || variables) { + if (usesVariables || variables || inlineVariables.size) { rules.add(inheritedVariables); if (inlineVariables.size) { variables = Object.assign( {}, - variables, inheritedVariables, + variables, ...Array.from(inlineVariables), { [VAR_SYMBOL]: true }, ); @@ -243,7 +251,7 @@ export function updateRules( } // Remove this component from the old observer - state.stylesObs?.cleanup(state.ruleEffect); + state.stylesObs?.unsubscribe(state.styleEffect); return { ...state, diff --git a/src/native/react/useNativeCss.ts b/src/native/react/useNativeCss.ts index 11d3ede8..f38c77c2 100644 --- a/src/native/react/useNativeCss.ts +++ b/src/native/react/useNativeCss.ts @@ -7,7 +7,7 @@ import { useState, type ComponentType, } from "react"; -import { Pressable, View } from "react-native"; +import { Image, Pressable, StyleSheet, View } from "react-native"; import { VariableContext } from "react-native-css/native-internal"; @@ -69,9 +69,6 @@ export function useNativeCss( const inheritedContainers = useContext(ContainerContext); const [state, setState] = useState((): ComponentState => { - // Both effects share the same observers to improve memory usage - const observers = new Set(); - /** * When fired, this effect will force the rules to be re-evaluated. * This will cause a re-render if there are different rules @@ -79,7 +76,7 @@ export function useNativeCss( * Use this when a rule condition changes, e.g FastRefresh or media queries */ const ruleEffect: Effect = { - observers, + observers: new Set(), run: () => setState((state) => updateRules(state)), }; @@ -90,11 +87,11 @@ export function useNativeCss( * Use this when a value changes, e.g vm units or light / dark mode */ const styleEffect: Effect = { - observers, + observers: new Set(), run: () => setState((state) => ({ ...state })), }; - return updateRules( + const initialState = updateRules( { ruleEffect, ruleEffectGetter: (observable) => observable.get(ruleEffect), @@ -110,10 +107,23 @@ export function useNativeCss( false, false, ); + // State initializers may be discarded by React StrictMode. Subscribe once + // the component commits instead of retaining an abandoned initializer. + cleanupEffect(ruleEffect); + return initialState; }); - // Both effects share the same observers, so we only need to cleanup one of them - useEffect(() => () => cleanupEffect(state.ruleEffect), [state.ruleEffect]); + useEffect(() => { + // Reconnect subscriptions after React replays an effect setup. + if (state.ruleEffect.observers.size === 0) { + state.ruleEffect.run(); + state.styleEffect.run(); + } + return () => { + cleanupEffect(state.ruleEffect); + cleanupEffect(state.styleEffect); + }; + }, [state.ruleEffect, state.styleEffect]); // Check if our derived state has changed (e.g the className prop) if ( @@ -137,7 +147,11 @@ export function useNativeCss( return createElement(Fragment); } - let props = getStyledProps(state, originalProps); + let props = getStyledProps( + state, + originalProps, + type === Image ? adaptImageProps : undefined, + ); if (type === View && props?.onPress) { type = Pressable; @@ -156,8 +170,18 @@ export function useNativeCss( } if (state.containers) { + // Publish a new props snapshot even when this component's own rules did not + // change. Descendants can depend on an ancestor attribute through a group. + const containers = Object.fromEntries( + Object.entries(state.containers).map(([name, container]) => [ + name, + container.key === state.ruleEffectGetter + ? { key: container.key, props: originalProps } + : (inheritedContainers[name] ?? container), + ]), + ); props = { - value: state.containers, + value: containers, children: createElement(type, props), }; type = ContainerContext.Provider; @@ -181,12 +205,12 @@ export function mappingToConfig(mapping: StyledConfiguration) { } else if (typeof value === "string") { return { source: key, target: value.split(".") }; } else if (typeof value === "object") { - const nativeStyleMapping = value.nativeStyleMapping + // Keep the declared deprecated alias working. The current spelling wins + // when both are provided, including an intentionally empty mapping. + const mapping = value.nativeStyleMapping ?? value.nativeStyleToProp; + const nativeStyleMapping = mapping ? Object.fromEntries( - Object.entries(value.nativeStyleMapping).map(([k, v]) => [ - k, - v === true ? k : v, - ]), + Object.entries(mapping).map(([k, v]) => [k, v === true ? k : v]), ) : undefined; @@ -214,3 +238,14 @@ export function mappingToConfig(mapping: StyledConfiguration) { throw new Error(`styled(): Invalid mapping for ${key}: ${value}`); }); } + +// Apply native Image fitting before normal, inline, and important props merge. +// The compiler's contentFit mapping remains available to Expo Image adapters. +function adaptImageProps(props: Record | undefined) { + if (!props || !("contentFit" in props)) return props; + const { contentFit, style, ...rest } = props; + return { + ...rest, + style: { ...StyleSheet.flatten(style), objectFit: contentFit }, + }; +} diff --git a/src/native/react/usePassthrough.ts b/src/native/react/usePassthrough.ts index bb1f0d40..fd80fb49 100644 --- a/src/native/react/usePassthrough.ts +++ b/src/native/react/usePassthrough.ts @@ -32,8 +32,9 @@ export function usePassthrough( if (Array.isArray(target)) { for (let i = 0; i < target.length - 1; i++) { const prop = target[i]!; - props[prop] ??= {}; - targetProps = props[prop]; + const value = targetProps[prop]; + targetProps[prop] = Array.isArray(value) ? [...value] : { ...value }; + targetProps = targetProps[prop]; } target = target[target.length - 1]!; } diff --git a/src/native/reactivity.ts b/src/native/reactivity.ts index 0824edeb..296988a6 100644 --- a/src/native/reactivity.ts +++ b/src/native/reactivity.ts @@ -10,7 +10,7 @@ import { import type { StyleDescriptor } from "react-native-css/compiler"; export type Effect = { - observers: Set; + observers: Set>; run(): void; }; @@ -19,6 +19,7 @@ export type Observable = { get: (effect?: Effect) => Value; set: (arg: Arg) => void; run: () => void; + unsubscribe: (effect: Effect) => void; }; type Read = (get: Getter, arg?: Arg) => Value; export type Getter = (observable: Observable) => Value; @@ -43,9 +44,10 @@ export function observable( const observers = new Set(); const effect: Effect = { - observers, + observers: new Set(), run: () => { if (!isStatic) { + cleanupEffect(effect); const nextValue = (init as Read)(getter, lastArg); if (equality(value, nextValue)) { return; @@ -57,14 +59,19 @@ export function observable( }, }; - const getter: Getter = (observable) => observable.get(effect); + const getter: Getter = (observable) => + observable.get(observers.size > 0 ? effect : undefined); - function get(effect?: Effect) { - if (effect) { - observers.add(effect); + function get(subscriber?: Effect) { + if (subscriber) { + if (observers.size === 0 && !isStatic) didInit = false; + observers.add(subscriber); + subscriber.observers.add(obs); } if (!didInit) { - value = (init as Read)(getter, undefined); + cleanupEffect(effect); + value = (init as Read)(getter, lastArg); + didInit = observers.size > 0; } return value; @@ -77,9 +84,10 @@ export function observable( } value = arg as unknown as Value; } else { + cleanupEffect(effect); const nextValue = (init as Read)(getter, arg); - didInit = true; + didInit = observers.size > 0; lastArg = arg; if (equality(value, nextValue)) { @@ -108,6 +116,13 @@ export function observable( get, set, run: effect.run, + unsubscribe(subscriber) { + if (observers.delete(subscriber) && observers.size === 0 && !isStatic) { + cleanupEffect(effect); + didInit = false; + } + subscriber.observers.delete(obs); + }, }; return obs; @@ -115,10 +130,11 @@ export function observable( export function cleanupEffect(effect: Effect) { if (!effect) return; - for (const dep of effect.observers) { - dep.observers.delete(effect); - } + const dependencies = Array.from(effect.observers); effect.observers.clear(); + for (const dep of dependencies) { + dep.unsubscribe(effect); + } } /** Family Helpers ************************************************************/ @@ -129,11 +145,9 @@ export function family( const map = new Map(); return Object.assign( (key: Key, args: Args) => { - let value = map.get(key); - if (!value) { - value = fn(key, args); - map.set(key, value); - } + if (map.has(key)) return map.get(key)!; + const value = fn(key, args); + map.set(key, value); return value; }, { @@ -166,11 +180,9 @@ export function weakFamily( const map = new WeakMap(); return Object.assign( (key: Key, args: Args) => { - let value = map.get(key); - if (!value) { - value = fn(key, args); - map.set(key, value); - } + if (map.has(key)) return map.get(key)!; + const value = fn(key, args); + map.set(key, value); return value; }, { @@ -216,14 +228,20 @@ Dimensions.addEventListener("change", ({ window }) => { /** Color Scheme **************************************************************/ -export const colorScheme = observable( +export const colorScheme = observable( Appearance.getColorScheme(), ); Appearance.addChangeListener((event) => colorScheme.set(event.colorScheme)); /** Containers ****************************************************************/ -export type ContainerContextValue = Record; +export type ContainerContextValue = Record< + string, + { + key: WeakKey; + props: Record | null | undefined; + } +>; export const ContainerContext = createContext({}); export const containerLayoutFamily = weakFamily(() => { @@ -243,6 +261,6 @@ export const containerWidthFamily = weakFamily((key) => { export const containerHeightFamily = weakFamily((key) => { return observable((read) => { - return read(containerLayoutFamily(key))?.width || 0; + return read(containerLayoutFamily(key))?.height || 0; }); }); diff --git a/src/native/reanimated.ts b/src/native/reanimated.ts index 03a14105..3dd57eec 100644 --- a/src/native/reanimated.ts +++ b/src/native/reanimated.ts @@ -11,11 +11,30 @@ export const animatedComponentFamily = weakFamily( return component; } - const createAnimatedComponent = - // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-member-access - require("react-native-reanimated").createAnimatedComponent as ( - component: ComponentType, - ) => ComponentType; + // Reanimated 4.5 keeps the original component display name. Compare its + // public built-in components by identity instead of interpreting that name. + const reanimated = + // eslint-disable-next-line @typescript-eslint/no-require-imports + require("react-native-reanimated") as typeof import("react-native-reanimated"); + const animated = reanimated.default; + if ( + [ + animated.View, + animated.Text, + animated.Image, + animated.ScrollView, + animated.FlatList, + ].some((value) => value === component) + ) { + return component; + } + + // This public constructor remains supported for arbitrary components; only + // its FlatList-specific overload is deprecated by Reanimated. + // eslint-disable-next-line @typescript-eslint/no-deprecated + const createAnimatedComponent = reanimated.createAnimatedComponent as ( + component: ComponentType, + ) => ComponentType; return createAnimatedComponent(component); }, diff --git a/src/native/styles/functions/color-mix.ts b/src/native/styles/functions/color-mix.ts index 27a95e04..c134e497 100644 --- a/src/native/styles/functions/color-mix.ts +++ b/src/native/styles/functions/color-mix.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import type { PlainColorObject } from "colorjs.io"; import { ColorSpace, to as convert, @@ -8,7 +6,6 @@ import { P3, parse, sRGB, - type ColorConstructor, } from "colorjs.io/fn"; import type { StyleFunctionResolver } from "../resolve"; @@ -18,49 +15,58 @@ ColorSpace.register(P3); ColorSpace.register(OKLab); export const colorMix: StyleFunctionResolver = (resolveValue, value) => { - const args = resolveValue(value[2]); - - if (!Array.isArray(args) || args.length < 3) { - return; - } + const resolved = resolveValue(value[2]); + if (!Array.isArray(resolved)) return; + // Resolved descriptors may be cached and shared by another styled component. + const args: unknown[] = [...(resolved as unknown[])]; try { const space = args.shift(); + const leftValue = args.shift(); + if (typeof space !== "string" || typeof leftValue !== "string") return; + ColorSpace.get(space); + const left = parse(leftValue); + const percentage = () => { + const next = args[0]; + if (typeof next !== "string" || !next.endsWith("%")) return undefined; + args.shift(); + return Number(next.slice(0, -1)) / 100; + }; + let leftWeight = percentage(); + const rightValue = args.shift(); + if (typeof rightValue !== "string") return; + const right = parse(rightValue); + let rightWeight = percentage(); + if (args.length) return; - let left: ColorConstructor | PlainColorObject = parse( - args.shift() as string, - ); - - let next = args.shift(); - - if (typeof next === "string" && next.endsWith("%")) { - left.alpha = parseFloat(next) / 100; - next = args.shift(); - } - - if (next === undefined) { - if (left.spaceId !== "srgb") { - left = convert(left, "srgb"); - } - - return `rgba(${(left.coords[0] ?? 0) * 255}, ${(left.coords[1] ?? 0) * 255}, ${(left.coords[2] ?? 0) * 255}, ${left.alpha})`; - } - - if (typeof next !== "string") { + // CSS Color 5: omitted weights are complementary, then both are normalized. + leftWeight ??= rightWeight === undefined ? 0.5 : 1 - rightWeight; + rightWeight ??= 1 - leftWeight; + if ( + ![leftWeight, rightWeight].every( + (weight) => Number.isFinite(weight) && weight >= 0 && weight <= 1, + ) + ) return; - } - const right = parse(next); + const sum = leftWeight + rightWeight; + if (sum === 0) return; + const alphaMultiplier = Math.min(sum, 1); - next = args.shift(); - if (next && typeof next === "string" && next.endsWith("%")) { - right.alpha = parseFloat(next) / 100; + let result; + if (right.alpha === 0 || left.alpha === 0) { + // A transparent endpoint contributes no premultiplied channels. + const source = right.alpha === 0 ? left : right; + const weight = right.alpha === 0 ? leftWeight : rightWeight; + result = convert(source, "srgb"); + result.alpha = ((source.alpha ?? 1) * weight) / sum; + } else { + result = mix(left, right, rightWeight / sum, { + space, + outputSpace: "srgb", + premultiplied: true, + }); } - - const result = mix(left, right, { - space, - outputSpace: "srgb", - }); - + result.alpha = (result.alpha ?? 1) * alphaMultiplier; return `rgba(${(result.coords[0] ?? 0) * 255}, ${(result.coords[1] ?? 0) * 255}, ${(result.coords[2] ?? 0) * 255}, ${result.alpha})`; } catch { return; diff --git a/src/native/styles/functions/filters.ts b/src/native/styles/functions/filters.ts index 98cf8fff..4a88a98a 100644 --- a/src/native/styles/functions/filters.ts +++ b/src/native/styles/functions/filters.ts @@ -97,3 +97,17 @@ export const dropShadow: StyleFunctionResolver = ( } : undefined; }; + +/** CSS variables may expand to multiple filters; native filter arrays must be flat. */ +export const filter: StyleFunctionResolver = (resolveValue, descriptor) => { + const value: unknown = resolveValue(descriptor[2]); + if (Array.isArray(value)) { + const filters: unknown[] = value.flat(Infinity); + return filters.filter( + (entry) => entry !== undefined && entry !== "initial", + ); + } + if (value === undefined || value === "initial") return; + if (value === "none") return null; + return typeof value === "string" ? value : [value]; +}; diff --git a/src/native/styles/functions/numeric-functions.ts b/src/native/styles/functions/numeric-functions.ts index 6ccc9dc9..84316ba2 100644 --- a/src/native/styles/functions/numeric-functions.ts +++ b/src/native/styles/functions/numeric-functions.ts @@ -23,16 +23,17 @@ export const min: StyleFunctionResolver = (resolveValue, value) => { export const clamp: StyleFunctionResolver = (resolveValue, value) => { const args = resolveValue(value[2]); - const [clampValue, min, max] = args as number[]; + if (!Array.isArray(args)) return; + + const [minimum, preferred, maximum] = args as unknown[]; if ( - !Array.isArray(args) || - typeof clampValue !== "number" || - typeof min !== "number" || - typeof max !== "number" + typeof minimum !== "number" || + typeof preferred !== "number" || + typeof maximum !== "number" ) { return; } - return Math.min(Math.max(clampValue, min), max); + return Math.max(minimum, Math.min(preferred, maximum)); }; diff --git a/src/native/styles/functions/string-functions.ts b/src/native/styles/functions/string-functions.ts index e4c741c9..8ab29a98 100644 --- a/src/native/styles/functions/string-functions.ts +++ b/src/native/styles/functions/string-functions.ts @@ -1,5 +1,23 @@ import type { StyleFunctionResolver } from "../resolve"; +export const gradientPosition: StyleFunctionResolver = ( + resolveValue, + value, +) => { + const position = resolveValue(value[2]); + return typeof position === "number" ? `${position}px` : position; +}; + +export const colorStop: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + if (!Array.isArray(args)) return args; + const [color, position] = args as unknown[]; + if (typeof color !== "string") return; + if (position === undefined) return color; + if (typeof position !== "number" && typeof position !== "string") return; + return `${color} ${typeof position === "number" ? `${position}px` : position}`; +}; + export const join: StyleFunctionResolver = (resolveValue, value) => { const args = resolveValue(value[2]); diff --git a/src/native/styles/functions/transform-functions.ts b/src/native/styles/functions/transform-functions.ts index c9826db6..9a38d15e 100644 --- a/src/native/styles/functions/transform-functions.ts +++ b/src/native/styles/functions/transform-functions.ts @@ -1,16 +1,17 @@ import { isStyleDescriptorArray } from "react-native-css/utilities"; import type { StyleFunctionResolver } from "../resolve"; +import { scaleFactor } from "../scale-factor"; export const scale: StyleFunctionResolver = (resolveValue, descriptor) => { const args = descriptor[2]; if (!isStyleDescriptorArray(args)) { - return { scale: resolveValue(args) }; + return { scale: scaleFactor(resolveValue(args)) }; } - const x = resolveValue(args[0]); - const y = resolveValue(args[1]); + const x = scaleFactor(resolveValue(args[0])); + const y = scaleFactor(resolveValue(args[1])); const isXValid = typeof x === "string" || typeof x === "number"; const isYValid = typeof y === "string" || typeof y === "number"; @@ -18,7 +19,7 @@ export const scale: StyleFunctionResolver = (resolveValue, descriptor) => { if (isXValid && isYValid) { return x === y ? { scale: x } : [{ scaleX: x }, { scaleY: y }]; } else if (isXValid) { - return { scaleX: x }; + return { scale: x }; } else if (isYValid) { return { scaleY: y }; } diff --git a/src/native/styles/index.ts b/src/native/styles/index.ts index c598fc6b..6be03daa 100644 --- a/src/native/styles/index.ts +++ b/src/native/styles/index.ts @@ -172,9 +172,10 @@ export const stylesFamily = family( /** * A family is a map, so we need to cleanup the observers when the the hash is no longer used */ + const unsubscribe = obs.unsubscribe; return Object.assign(obs, { - cleanup: (effect: Effect) => { - obs.observers.delete(effect); + unsubscribe: (effect: Effect) => { + unsubscribe(effect); if (obs.observers.size === 0) { stylesFamily.delete(hash); } @@ -186,29 +187,23 @@ export const stylesFamily = family( export function getStyledProps( state: ComponentState, inline: Record | undefined | null, + adaptProps: ( + props: Record | undefined, + ) => Record | undefined = (props) => props, ) { let result: Record | undefined; const styledProps = state.stylesObs?.get(state.styleEffect); - // When multiple configs exist (e.g. ScrollView with className→style and - // contentContainerClassName→contentContainerStyle), each iteration of - // deepMergeConfig produces a full props object via Object.assign({}, left, right). - // Later iterations overwrite earlier ones' correctly-merged target props. - // We save each iteration's target value and restore them after the loop. - // - // Note: This uses the leaf key of config.target for storage/restoration. - // For nested array targets (length > 1), the leaf key is stored at the - // top level, which is correct because deepMergeConfig already builds the - // nested structure. If two configs ever share the same leaf key, the last - // one wins — but no built-in component mapping produces this scenario. - const computedTargets: Record = {}; + // Each config merges a complete props object. Preserve its full target path + // before the next config merges unrelated inline props over that object. + const computedTargets: { path: string[]; value: unknown }[] = []; const consumedSources: string[] = []; for (const config of state.configs) { result = deepMergeConfig( config, - nativeStyleMapping(config, styledProps?.normal), + nativeStyleMapping(config, adaptProps(styledProps?.normal)), inline, true, ); @@ -217,17 +212,19 @@ export function getStyledProps( result = deepMergeConfig( config, result, - nativeStyleMapping(config, styledProps.important), + nativeStyleMapping(config, adaptProps(styledProps.important)), ); } - // Save the correctly-merged target prop from this iteration if (result && config.target) { - const targetKey = Array.isArray(config.target) - ? config.target[config.target.length - 1] - : config.target; - if (targetKey && targetKey in result) { - computedTargets[targetKey] = result[targetKey]; + const path = Array.isArray(config.target) + ? config.target + : [config.target]; + let target = result; + for (const key of path.slice(0, -1)) target = target?.[key]; + const key = path[path.length - 1]; + if (target && key && key in target) { + computedTargets.push({ path, value: target[key] }); } } @@ -297,8 +294,15 @@ export function getStyledProps( // Restore correctly-merged target props that may have been overwritten // by later config iterations' Object.assign({}, left, right) if (result) { - for (const key in computedTargets) { - result[key] = computedTargets[key]; + for (const { path, value } of computedTargets) { + let target = result; + for (const key of path.slice(0, -1)) { + const existing = target[key]; + target = target[key] = Array.isArray(existing) + ? [...existing] + : { ...existing }; + } + target[path[path.length - 1]!] = value; } for (const source of consumedSources) { delete result[source]; @@ -462,15 +466,10 @@ function deepMergeConfig( * If target is a path, deep merge until we get to the last key */ if (Array.isArray(config.target)) { - for (let i = 0; i < config.target.length - 1; i++) { - const key = config.target[i]; - - if (key === undefined) { - return result; - } - + if (config.target.length > 1) { + const key = config.target[0]!; result[key] = deepMergeConfig( - { source: config.source, target: config.target.slice(i + 1) }, + { source: config.source, target: config.target.slice(1) }, left?.[key], right?.[key], rightIsInline, @@ -548,7 +547,11 @@ function nativeStyleMapping( config: Config, props: Record | undefined, ) { - if (!config.nativeStyleMapping || !props) { + if (!props) { + return props; + } + if (!config.nativeStyleMapping) { + if (config.target === false) delete props.style; return props; } @@ -597,5 +600,6 @@ function nativeStyleMapping( target[lastToken!] = styleValue; } + if (config.target === false) delete props.style; return props; } diff --git a/src/native/styles/resolve.ts b/src/native/styles/resolve.ts index 8465e9b1..0cb40385 100644 --- a/src/native/styles/resolve.ts +++ b/src/native/styles/resolve.ts @@ -5,6 +5,7 @@ import type { StyleDescriptor, StyleFunction, } from "react-native-css/compiler"; +import { isStyleDescriptorArray } from "react-native-css/utilities"; import type { RenderGuard } from "../conditions/guards"; import { type Getter, type VariableContextValue } from "../reactivity"; @@ -12,6 +13,7 @@ import type { calculateProps } from "./calculate-props"; import { transformKeys } from "./defaults"; import * as functions from "./functions"; import { lineHeight } from "./line-height"; +import { scaleFactor } from "./scale-factor"; import * as shorthands from "./shorthands"; import { em, rem, vh, vw } from "./units"; import { varResolver } from "./variables"; @@ -51,6 +53,7 @@ export type ResolveValueOptions = { inlineVariables?: InlineVariable | undefined; renderGuards?: RenderGuard[]; variableHistory?: Set; + variableCycles?: Set; /** Pass down to perform recursive calculations and avoid circular dependencies */ calculateProps?: typeof calculateProps; }; @@ -77,7 +80,7 @@ export function resolveValue( return null; } else if (value.endsWith("px")) { // Inline vars() might set a value with a px suffix - return parseInt(value.slice(0, -2), 10); + return parseFloat(value.slice(0, -2)); } else { return value; } @@ -87,7 +90,7 @@ export function resolveValue( return value; } - if (isDescriptorArray(value)) { + if (isStyleDescriptorArray(value)) { value = value .map((d) => resolveValue(d, get, options)) .filter((d) => d !== undefined); @@ -122,7 +125,16 @@ export function resolveValue( ) as StyleDescriptor; } else if (transformKeys.has(name)) { // translate, rotate, scale, etc. - return { [name]: simpleResolve(value[2], castToArray) }; + let resolved = simpleResolve(value[2], castToArray); + // CSS scale percentages describe a factor, unlike translation percentages. + if ( + (name === "scaleX" || name === "scaleY" || name === "scale") && + typeof resolved === "string" && + resolved.endsWith("%") + ) { + resolved = scaleFactor(resolved); + } + return { [name]: resolved }; } else { let args = simpleResolve(value[2], castToArray); @@ -154,11 +166,3 @@ export function resolveValue( } } } - -function isDescriptorArray( - value: StyleDescriptor | StyleDescriptor[], -): value is StyleDescriptor[] { - return Array.isArray(value) && typeof value[0] === "object" - ? Array.isArray(value[0]) - : true; -} diff --git a/src/native/styles/scale-factor.ts b/src/native/styles/scale-factor.ts new file mode 100644 index 00000000..5d30789a --- /dev/null +++ b/src/native/styles/scale-factor.ts @@ -0,0 +1,6 @@ +export function scaleFactor(value: unknown) { + if (typeof value === "string" && value.endsWith("%")) { + return Number(value.slice(0, -1)) / 100; + } + return value; +} diff --git a/src/native/styles/shorthands/animation.ts b/src/native/styles/shorthands/animation.ts index 9b5cc39a..f28aacd9 100644 --- a/src/native/styles/shorthands/animation.ts +++ b/src/native/styles/shorthands/animation.ts @@ -39,6 +39,8 @@ export const animationShorthand = shorthandHandler( [name], [duration, name], [name, duration], + [name, duration, timingFunction], + [duration, timingFunction, name], [name, duration, iteration], [name, duration, timingFunction, iteration], [duration, delay, name], @@ -67,7 +69,7 @@ export const animation: StyleFunctionResolver = ( ) => { const animationShortHandTuples = animationShorthand( resolveValue, - value, + [value[2]], get, options, ); @@ -86,6 +88,10 @@ export const animation: StyleFunctionResolver = ( return; } + if (name === "none") { + return applyShorthand(animationShortHandTuples); + } + const keyframes = get(StyleCollection.keyframes(name)); const animation: Record = {}; @@ -127,5 +133,5 @@ export const animationName: StyleFunctionResolver = ( options, ) => { const shorthand: any = animation(resolveValue, value, get, options); - return shorthand.animationName; + return shorthand?.animationName; }; diff --git a/src/native/styles/shorthands/index.ts b/src/native/styles/shorthands/index.ts index c9c8f33c..f9ed4196 100644 --- a/src/native/styles/shorthands/index.ts +++ b/src/native/styles/shorthands/index.ts @@ -1,5 +1,6 @@ export * from "./animation"; export * from "./border"; +export * from "./logical-border-width"; export * from "./box-shadow"; export * from "./text-shadow"; export * from "./transform"; diff --git a/src/native/styles/shorthands/logical-border-width.ts b/src/native/styles/shorthands/logical-border-width.ts new file mode 100644 index 00000000..147a3517 --- /dev/null +++ b/src/native/styles/shorthands/logical-border-width.ts @@ -0,0 +1,32 @@ +import { ShortHandSymbol } from "../constants"; +import type { StyleFunctionResolver } from "../resolve"; + +function logicalWidth(start: string, end: string): StyleFunctionResolver { + return (resolve, descriptor) => { + const resolved = resolve(descriptor[2]); + const values: unknown[] = Array.isArray(resolved) ? resolved : [resolved]; + if ( + values.length < 1 || + values.length > 2 || + !values.every( + (value) => + typeof value === "number" && Number.isFinite(value) && value >= 0, + ) + ) + return; + return { + [ShortHandSymbol]: true, + [start]: values[0], + [end]: values[1] ?? values[0], + }; + }; +} + +export const borderBlockWidth = logicalWidth( + "borderTopWidth", + "borderBottomWidth", +); +export const borderInlineWidth = logicalWidth( + "borderStartWidth", + "borderEndWidth", +); diff --git a/src/native/styles/variables.ts b/src/native/styles/variables.ts index af3d6ee2..0b785645 100644 --- a/src/native/styles/variables.ts +++ b/src/native/styles/variables.ts @@ -18,7 +18,8 @@ export function varResolver( renderGuards, inheritedVariables: variables = { [VAR_SYMBOL]: true }, inlineVariables, - variableHistory = new Set(), + variableHistory = (options.variableHistory ??= new Set()), + variableCycles = (options.variableCycles ??= new Set()), } = options; const args = fn[2]; @@ -29,11 +30,10 @@ export function varResolver( if (typeof args === "string") { name = args; } else { - const result = resolve(args); - - if (isStyleDescriptorArray(result)) { - name = result[0] as string; - fallback = result[1]; + // Fallbacks are substituted only when needed, after reading the variable. + if (isStyleDescriptorArray(args)) { + name = args[0] as string; + fallback = args[1]; } } @@ -41,48 +41,58 @@ export function varResolver( return; } - // If this recurses back to the same variable, we need to stop + if (variableCycles.has(name)) return resolve(fallback); + + // Share the active resolution path with recursive calls. Mark only the + // actual cycle, so a dependent variable can still use its own fallback. if (variableHistory.has(name)) { + let inCycle = false; + for (const dependency of variableHistory) { + if (dependency === name) inCycle = true; + if (inCycle) variableCycles.add(dependency); + } return; } - if (name in variables) { - renderGuards?.push(["v", name, variables[name]]); - return resolve(variables[name]); - } - variableHistory.add(name); - let value = resolve(inlineVariables?.[name] as StyleDescriptor); - if (value !== undefined) { - options.inlineVariables ??= { [VAR_SYMBOL]: "inline" }; - options.inlineVariables[name] = value; + try { + let value = resolve(inlineVariables?.[name] as StyleDescriptor); + if (variableCycles.has(name)) return resolve(fallback); + if (value !== undefined) { + options.inlineVariables ??= { [VAR_SYMBOL]: "inline" }; + options.inlineVariables[name] = value; - return value; - } + return value; + } - value = resolve(variables[name]); - if (value !== undefined) { - renderGuards?.push(["v", name, value]); - options.inlineVariables ??= { [VAR_SYMBOL]: "inline" }; - options.inlineVariables[name] = value; + // A universal selector declares the variable on this element. It has lower + // specificity than the element's class declarations, but beats inheritance. + value = resolve(get(universalVariables(name))); + if (variableCycles.has(name)) return resolve(fallback); + if (value !== undefined) { + options.inlineVariables ??= { [VAR_SYMBOL]: "inline" }; + options.inlineVariables[name] = value; + return value; + } - return value; - } + // Guards retain the ancestor's raw descriptor, matching the context value. + if (name in variables) { + renderGuards?.push(["v", name, variables[name]]); + value = resolve(variables[name]); + return variableCycles.has(name) ? resolve(fallback) : value; + } - value = resolve(get(universalVariables(name))); - if (value !== undefined) { - options.inlineVariables ??= { [VAR_SYMBOL]: "inline" }; - options.inlineVariables[name] = value; - return value; - } + value = resolve(get(rootVariables(name))); + if (variableCycles.has(name)) return resolve(fallback); + if (value !== undefined) { + options.inlineVariables ??= { [VAR_SYMBOL]: "inline" }; + options.inlineVariables[name] = value; + return value; + } - value = resolve(get(rootVariables(name))); - if (value !== undefined) { - options.inlineVariables ??= { [VAR_SYMBOL]: "inline" }; - options.inlineVariables[name] = value; - return value; + return resolve(fallback); + } finally { + variableHistory.delete(name); } - - return resolve(fallback); } diff --git a/src/runtime.types.ts b/src/runtime.types.ts index 6e8f9ad8..69cab6ca 100644 --- a/src/runtime.types.ts +++ b/src/runtime.types.ts @@ -3,6 +3,7 @@ import type { ClassicComponentClass, ComponentClass, ComponentProps, + ComponentPropsWithRef, ComponentType, ForwardRefExoticComponent, FunctionComponent, @@ -44,20 +45,24 @@ export type StyledProps> = P & { : never]?: string; }; -export type Styled = < - const C extends ReactComponent, - const M extends StyledConfiguration, ->( - component: C, - mapping: M & StyledConfiguration, - options?: StyledOptions, -) => StyledComponent; +export interface Styled { + ( + component: C, + mapping?: undefined, + options?: StyledOptions, + ): ComponentType & { className?: string }>; + >( + component: C, + mapping: M & StyledConfiguration, + options?: StyledOptions, + ): StyledComponent; +} type StyledComponent< C extends ReactComponent, M extends StyledConfiguration, > = ComponentType< - ComponentProps & { + ComponentPropsWithRef & { [K in keyof M as K extends string ? M[K] extends undefined | false ? never @@ -90,10 +95,7 @@ interface StyledConfigurationObject< ComponentProps >; /** @deprecated Please use nativeStyleMapping */ - nativeStyleToProp?: NativeStyleMapping< - ResolveDotPath>, - ComponentProps - >; + nativeStyleToProp?: StyledConfigurationObject["nativeStyleMapping"]; } type NativeStyleMapping = T extends object @@ -145,6 +147,6 @@ export type RNStyle = ViewStyle & TextStyle & ImageStyle; /******************************** Globals ********************************/ export interface ColorScheme { - get: () => ColorSchemeName; - set: (value: ColorSchemeName) => void; + get: () => ColorSchemeName | null | undefined; + set: (value: ColorSchemeName | null | undefined) => void; } diff --git a/src/utilities/dot-notation.types.ts b/src/utilities/dot-notation.types.ts index 6700a21a..dff98a94 100644 --- a/src/utilities/dot-notation.types.ts +++ b/src/utilities/dot-notation.types.ts @@ -1,4 +1,6 @@ /* eslint-disable */ +import type { Component, ReactElement } from "react"; + // ---------- Base Utilities ---------- type Falsy = undefined | null | false | ""; @@ -32,14 +34,13 @@ export type StyleProp = type UnwrapRecursiveArray< T, Depth extends unknown[] = [], - MaxDepth extends number = 10 + MaxDepth extends number = 10, > = Depth["length"] extends MaxDepth ? T : T extends (infer I)[] ? UnwrapRecursiveArray : T; - // Remove null, false, undefined, etc. type RemoveFalsy = Exclude; @@ -53,7 +54,7 @@ type ExtractStyleObject = RemoveRegisteredStyle< // Check if something is a non-array plain object type IsPlainObject = T extends object - ? T extends Function + ? T extends Function | Component | ReactElement ? false : T extends readonly any[] ? false diff --git a/src/utilities/style-descriptor.ts b/src/utilities/style-descriptor.ts index 1310d62b..7a166fa1 100644 --- a/src/utilities/style-descriptor.ts +++ b/src/utilities/style-descriptor.ts @@ -3,22 +3,17 @@ import type { StyleDescriptor, StyleFunction } from "react-native-css/compiler"; export function isStyleDescriptorArray( value: unknown, ): value is StyleDescriptor[] { - if (Array.isArray(value)) { - // If its an array and the first item is an object, the only allowed value is an array - return typeof value[0] === "object" ? Array.isArray(value[0]) : true; - } - - return false; + return Array.isArray(value) && !isStyleFunction(value); } -export function isStyleFunction( - value: StyleDescriptor, -): value is StyleFunction { - if (Array.isArray(value)) { - return typeof value[0] === "object" - ? Object.keys(value[0]).length === 0 - : false; - } - - return false; +export function isStyleFunction(value: unknown): value is StyleFunction { + if (!Array.isArray(value)) return false; + const marker: unknown = value[0]; + return ( + marker !== null && + typeof marker === "object" && + !Array.isArray(marker) && + Object.keys(marker).length === 0 && + typeof value[1] === "string" + ); } diff --git a/src/web/api.tsx b/src/web/api.tsx index e43f800a..28e09ab8 100644 --- a/src/web/api.tsx +++ b/src/web/api.tsx @@ -1,7 +1,6 @@ import { createElement, useMemo, - type ComponentPropsWithRef, type ComponentType, type PropsWithChildren, } from "react"; @@ -12,25 +11,21 @@ import type { Props, StyledConfiguration, StyledOptions, - StyledProps, } from "react-native-css"; -import type { ReactComponent } from "../runtime.types"; +import type { ReactComponent, Styled } from "../runtime.types"; import { assignStyle } from "./assign-style"; const defaultMapping: StyledConfiguration> = { className: "style", }; -export const styled = < - const C extends ReactComponent, - const M extends StyledConfiguration, ->( - baseComponent: C, - mapping: M = defaultMapping as M, +export const styled: Styled = ( + baseComponent: ReactComponent, + mapping: StyledConfiguration = defaultMapping, _options?: StyledOptions, ) => { - return (props: StyledProps, M>) => { + return (props: Props) => { return useCssElement(baseComponent, props, mapping); }; }; @@ -73,7 +68,7 @@ export const colorScheme: ColorScheme = { return Appearance.getColorScheme(); }, set(name) { - Appearance.setColorScheme(name); + Appearance.setColorScheme(name ?? "unspecified"); }, }; diff --git a/src/web/assign-style.ts b/src/web/assign-style.ts index b49bfa51..09c3d591 100644 --- a/src/web/assign-style.ts +++ b/src/web/assign-style.ts @@ -19,7 +19,8 @@ export function assignStyle( } return props; } else { - props[target] ??= {}; + const existing = props[target]; + props[target] = Array.isArray(existing) ? [...existing] : { ...existing }; assignStyle(value, targets, props[target]); return props; } diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..0c7103d4 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "include": ["src", "types.d.ts"], + "exclude": [ + "node_modules", + "dist", + "src/__tests__", + "src/__fixtures__", + "src/**/*.test.*" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 1d83b3f5..248f4129 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "types": ["node", "jest", "react"], "rootDir": ".", "paths": { "react-native-css": [ @@ -41,4 +42,4 @@ "node_modules", "dist", ] -} \ No newline at end of file +} diff --git a/types.d.ts b/types.d.ts index 1ba5b856..924b9a7d 100644 --- a/types.d.ts +++ b/types.d.ts @@ -25,7 +25,6 @@ declare module "react-native" { ScrollViewPropsAndroid, Touchable { contentContainerClassName?: string; - indicatorClassName?: string; } interface FlatListProps extends VirtualizedListProps { columnWrapperClassName?: string; @@ -35,39 +34,20 @@ declare module "react-native" { } interface ImagePropsBase { className?: string; - cssInterop?: boolean; } interface ViewProps { className?: string; - cssInterop?: boolean; - } - interface TextInputProps { - placeholderClassName?: string; } interface TextProps { className?: string; - cssInterop?: boolean; } interface SwitchProps { className?: string; - cssInterop?: boolean; - } - interface InputAccessoryViewProps { - className?: string; - cssInterop?: boolean; } interface TouchableWithoutFeedbackProps { className?: string; - cssInterop?: boolean; - } - interface StatusBarProps { - className?: string; - cssInterop?: boolean; } interface KeyboardAvoidingViewProps extends ViewProps { contentContainerClassName?: string; } - interface ModalBaseProps { - presentationClassName?: string; - } } diff --git a/yarn.lock b/yarn.lock index 8fe7596e..ced6f69e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,18 +26,6 @@ __metadata: languageName: node linkType: hard -"@0no-co/graphql.web@npm:^1.0.13, @0no-co/graphql.web@npm:^1.0.8": - version: 1.2.0 - resolution: "@0no-co/graphql.web@npm:1.2.0" - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 - peerDependenciesMeta: - graphql: - optional: true - checksum: 10c0/4eed600962bfab42afb49cddcfb31a47b00502f59707609cf160559920ce0f5cf8874791e4cafc465ede30ae291992f3f892bc757b2a989e80e50e358f71c518 - languageName: node - linkType: hard - "@alloc/quick-lru@npm:^5.2.0": version: 5.2.0 resolution: "@alloc/quick-lru@npm:5.2.0" @@ -61,15 +49,6 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:7.10.4, @babel/code-frame@npm:~7.10.4": - version: 7.10.4 - resolution: "@babel/code-frame@npm:7.10.4" - dependencies: - "@babel/highlight": "npm:^7.10.4" - checksum: 10c0/69e0f52986a1f40231d891224f420436629b6678711b68c088e97b7bdba1607aeb5eb9cfb070275c433f0bf43c37c134845db80d1cdbf5ac88a69b0bdcce9402 - languageName: node - linkType: hard - "@babel/code-frame@npm:7.23.5": version: 7.23.5 resolution: "@babel/code-frame@npm:7.23.5" @@ -80,7 +59,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.20.0, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.27.1": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.20.0, @babel/code-frame@npm:^7.27.1": version: 7.27.1 resolution: "@babel/code-frame@npm:7.27.1" dependencies: @@ -102,7 +81,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.29.7": +"@babel/code-frame@npm:^7.29.0, @babel/code-frame@npm:^7.29.7": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" dependencies: @@ -113,6 +92,15 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:~7.10.4": + version: 7.10.4 + resolution: "@babel/code-frame@npm:7.10.4" + dependencies: + "@babel/highlight": "npm:^7.10.4" + checksum: 10c0/69e0f52986a1f40231d891224f420436629b6678711b68c088e97b7bdba1607aeb5eb9cfb070275c433f0bf43c37c134845db80d1cdbf5ac88a69b0bdcce9402 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.27.2, @babel/compat-data@npm:^7.27.7": version: 7.28.4 resolution: "@babel/compat-data@npm:7.28.4" @@ -127,7 +115,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.20.0, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.2, @babel/core@npm:^7.28.0, @babel/core@npm:^7.29.0": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.20.0, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.2, @babel/core@npm:^7.29.0": version: 7.29.7 resolution: "@babel/core@npm:7.29.7" dependencies: @@ -150,7 +138,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.20.5, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.26.2, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": +"@babel/generator@npm:^7.20.5, @babel/generator@npm:^7.26.2, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": version: 7.28.3 resolution: "@babel/generator@npm:7.28.3" dependencies: @@ -163,6 +151,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.29.1, @babel/generator@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/generator@npm:7.29.8" + dependencies: + "@babel/parser": "npm:^7.29.8" + "@babel/types": "npm:^7.29.8" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/7b896696314a659652393b76d78276e236acd0f7fae40a9a1af7f01c76aeafc630dd0966aad6f9386d35d7c674a8e6e2d8e217c44d25fb11460e68afa9ba8441 + languageName: node + linkType: hard + "@babel/generator@npm:^7.29.7": version: 7.29.7 resolution: "@babel/generator@npm:7.29.7" @@ -194,7 +195,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": +"@babel/helper-compilation-targets@npm:^7.27.2": version: 7.27.2 resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: @@ -594,7 +595,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.26.2, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.26.2, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": version: 7.28.4 resolution: "@babel/parser@npm:7.28.4" dependencies: @@ -605,6 +606,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.29.0, @babel/parser@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/parser@npm:7.29.8" + dependencies: + "@babel/types": "npm:^7.29.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/acc890c5e6a6dd40863a47b50bac111d7185ee6fbbe163ebe11d5214854ca2adb901462ad4d718a65090ef84bd2230e9e8ab45a2e0caccc685f1f57ab0bb1e28 + languageName: node + linkType: hard + "@babel/parser@npm:^7.29.7": version: 7.29.7 resolution: "@babel/parser@npm:7.29.7" @@ -1018,18 +1030,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.0.0-0, @babel/plugin-transform-arrow-functions@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/19abd7a7d11eef58c9340408a4c2594503f6c4eaea1baa7b0e5fbdda89df097e50663edb3448ad2300170b39efca98a75e5767af05cad3b0facb4944326896a3 - languageName: node - linkType: hard - -"@babel/plugin-transform-arrow-functions@npm:^7.29.7": +"@babel/plugin-transform-arrow-functions@npm:^7.27.1, @babel/plugin-transform-arrow-functions@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-arrow-functions@npm:7.29.7" dependencies: @@ -1125,7 +1126,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.0.0-0, @babel/plugin-transform-class-properties@npm:^7.25.4": +"@babel/plugin-transform-class-properties@npm:^7.25.4": version: 7.27.1 resolution: "@babel/plugin-transform-class-properties@npm:7.27.1" dependencies: @@ -1137,7 +1138,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.29.7": +"@babel/plugin-transform-class-properties@npm:^7.28.6, @babel/plugin-transform-class-properties@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-class-properties@npm:7.29.7" dependencies: @@ -1173,7 +1174,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.0.0-0, @babel/plugin-transform-classes@npm:^7.25.4": +"@babel/plugin-transform-classes@npm:^7.25.4": version: 7.28.4 resolution: "@babel/plugin-transform-classes@npm:7.28.4" dependencies: @@ -1189,7 +1190,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.29.7": +"@babel/plugin-transform-classes@npm:^7.28.6, @babel/plugin-transform-classes@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-classes@npm:7.29.7" dependencies: @@ -1205,18 +1206,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-computed-properties@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/template": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e09a12f8c8ae0e6a6144c102956947b4ec05f6c844169121d0ec4529c2d30ad1dc59fee67736193b87a402f44552c888a519a680a31853bdb4d34788c28af3b0 - languageName: node - linkType: hard - "@babel/plugin-transform-computed-properties@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-computed-properties@npm:7.29.7" @@ -1392,19 +1381,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.25.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-function-name@npm:7.27.1" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.27.1" - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5abdc7b5945fbd807269dcc6e76e52b69235056023b0b35d311e8f5dfd6c09d9f225839798998fc3b663f50cf701457ddb76517025a0d7a5474f3fe56e567a4c - languageName: node - linkType: hard - "@babel/plugin-transform-function-name@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-function-name@npm:7.29.7" @@ -1429,17 +1405,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.25.2": - version: 7.27.1 - resolution: "@babel/plugin-transform-literals@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c40dc3eb2f45a92ee476412314a40e471af51a0f51a24e91b85cef5fc59f4fe06758088f541643f07f949d2c67ee7bdce10e11c5ec56791ae09b15c3b451eeca - languageName: node - linkType: hard - "@babel/plugin-transform-literals@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-literals@npm:7.29.7" @@ -1581,7 +1546,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.0.0-0, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": version: 7.27.1 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1" dependencies: @@ -1592,7 +1557,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.29.7": +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.28.6, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.29.7" dependencies: @@ -1603,17 +1568,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/b72cbebbfe46fcf319504edc1cf59f3f41c992dd6840db766367f6a1d232cd2c52143c5eaf57e0316710bee251cae94be97c6d646b5022fcd9274ccb131b470c - languageName: node - linkType: hard - "@babel/plugin-transform-numeric-separator@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-numeric-separator@npm:7.29.7" @@ -1689,7 +1643,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.0.0-0, @babel/plugin-transform-optional-chaining@npm:^7.24.8": +"@babel/plugin-transform-optional-chaining@npm:^7.24.8": version: 7.27.1 resolution: "@babel/plugin-transform-optional-chaining@npm:7.27.1" dependencies: @@ -1701,7 +1655,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.29.7": +"@babel/plugin-transform-optional-chaining@npm:^7.28.6, @babel/plugin-transform-optional-chaining@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-optional-chaining@npm:7.29.7" dependencies: @@ -1796,7 +1750,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.24.7, @babel/plugin-transform-react-display-name@npm:^7.27.1": +"@babel/plugin-transform-react-display-name@npm:^7.24.7": version: 7.28.0 resolution: "@babel/plugin-transform-react-display-name@npm:7.28.0" dependencies: @@ -1841,43 +1795,28 @@ __metadata: linkType: hard "@babel/plugin-transform-react-jsx-self@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-jsx-self@npm:7.27.1" + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/00a4f917b70a608f9aca2fb39aabe04a60aa33165a7e0105fd44b3a8531630eb85bf5572e9f242f51e6ad2fa38c2e7e780902176c863556c58b5ba6f6e164031 + checksum: 10c0/288995f0fd0d61ab740a315fb56c8255eb87dd4a4ac2ac7d0fdd4ce173c3878200141e80da2db0e598c7b2a71e74e604afdbb4c8e14ae6e0527ce0b6294c03da languageName: node linkType: hard "@babel/plugin-transform-react-jsx-source@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-jsx-source@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5e67b56c39c4d03e59e03ba80692b24c5a921472079b63af711b1d250fc37c1733a17069b63537f750f3e937ec44a42b1ee6a46cd23b1a0df5163b17f741f7f2 - languageName: node - linkType: hard - -"@babel/plugin-transform-react-jsx@npm:^7.25.2, @babel/plugin-transform-react-jsx@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-jsx@npm:7.27.1" + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.29.7" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.1" - "@babel/helper-module-imports": "npm:^7.27.1" - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/plugin-syntax-jsx": "npm:^7.27.1" - "@babel/types": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/1a08637c39fc78c9760dd4a3ed363fdbc762994bf83ed7872ad5bda0232fcd0fc557332f2ce36b522c0226dfd9cc8faac6b88eddda535f24825198a689e571af + checksum: 10c0/a121899631e6d99b9e1b276acf736dbb77948a31f8eeeae67b89c8a4ab0f05e51ba64544baa06c286a2b9944f227244e15aac464e2313d286d0511fe51e27975 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.29.7": +"@babel/plugin-transform-react-jsx@npm:^7.25.2, @babel/plugin-transform-react-jsx@npm:^7.28.6, @babel/plugin-transform-react-jsx@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-react-jsx@npm:7.29.7" dependencies: @@ -1892,6 +1831,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx@npm:7.27.1" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/1a08637c39fc78c9760dd4a3ed363fdbc762994bf83ed7872ad5bda0232fcd0fc557332f2ce36b522c0226dfd9cc8faac6b88eddda535f24825198a689e571af + languageName: node + linkType: hard + "@babel/plugin-transform-react-pure-annotations@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.27.1" @@ -1917,13 +1871,13 @@ __metadata: linkType: hard "@babel/plugin-transform-regenerator@npm:^7.24.7": - version: 7.28.4 - resolution: "@babel/plugin-transform-regenerator@npm:7.28.4" + version: 7.29.8 + resolution: "@babel/plugin-transform-regenerator@npm:7.29.8" dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/5ad14647ffaac63c920e28df1b580ee2e932586bbdc71f61ec264398f68a5406c71a7f921de397a41b954a69316c5ab90e5d789ffa2bb34c5e6feb3727cfefb8 + checksum: 10c0/768da9bc3c8cbb0c591bc7db050215b8cea44df9df525d5cd6034aa179b091c2321a745b68139b3fc70b4493f2df1fc99a57acd4029c6d355d32a8ed8bd69f82 languageName: node linkType: hard @@ -1977,18 +1931,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.0.0-0, @babel/plugin-transform-shorthand-properties@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/bd5544b89520a22c41a6df5ddac9039821d3334c0ef364d18b0ba9674c5071c223bcc98be5867dc3865cb10796882b7594e2c40dedaff38e1b1273913fe353e1 - languageName: node - linkType: hard - -"@babel/plugin-transform-shorthand-properties@npm:^7.29.7": +"@babel/plugin-transform-shorthand-properties@npm:^7.27.1, @babel/plugin-transform-shorthand-properties@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.29.7" dependencies: @@ -1999,18 +1942,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-spread@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/b34fc58b33bd35b47d67416655c2cbc8578fbb3948b4592bc15eb6d8b4046986e25c06e3b9929460fa4ab08e9653582415e7ef8b87d265e1239251bdf5a4c162 - languageName: node - linkType: hard - "@babel/plugin-transform-spread@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-spread@npm:7.29.7" @@ -2023,17 +1954,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5698df2d924f0b1b7bdb7ef370e83f99ed3f0964eb3b9c27d774d021bee7f6d45f9a73e2be369d90b4aff1603ce29827f8743f091789960e7669daf9c3cda850 - languageName: node - linkType: hard - "@babel/plugin-transform-sticky-regex@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-sticky-regex@npm:7.29.7" @@ -2056,18 +1976,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.0.0-0": - version: 7.27.1 - resolution: "@babel/plugin-transform-template-literals@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c90f403e42ef062b60654d1c122c70f3ec6f00c2f304b0931ebe6d0b432498ef8a5ef9266ddf00debc535f8390842207e44d3900eff1d2bab0cc1a700f03e083 - languageName: node - linkType: hard - -"@babel/plugin-transform-template-literals@npm:^7.29.7": +"@babel/plugin-transform-template-literals@npm:^7.27.1, @babel/plugin-transform-template-literals@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-template-literals@npm:7.29.7" dependencies: @@ -2142,7 +2051,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.0.0-0, @babel/plugin-transform-unicode-regex@npm:^7.24.7": +"@babel/plugin-transform-unicode-regex@npm:^7.24.7": version: 7.27.1 resolution: "@babel/plugin-transform-unicode-regex@npm:7.27.1" dependencies: @@ -2154,7 +2063,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.29.7": +"@babel/plugin-transform-unicode-regex@npm:^7.27.1, @babel/plugin-transform-unicode-regex@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-unicode-regex@npm:7.29.7" dependencies: @@ -2272,22 +2181,6 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:^7.22.15": - version: 7.27.1 - resolution: "@babel/preset-react@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/helper-validator-option": "npm:^7.27.1" - "@babel/plugin-transform-react-display-name": "npm:^7.27.1" - "@babel/plugin-transform-react-jsx": "npm:^7.27.1" - "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" - "@babel/plugin-transform-react-pure-annotations": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/a80b02ef08b026cb9830d6512d08c7cd378eef4c0631dacba4aa1106240d9bb76af6373463f0255f4bbdbfcce40375a61e92735375906ba5871629b0c314bc45 - languageName: node - linkType: hard - "@babel/preset-react@npm:^7.28.5": version: 7.29.7 resolution: "@babel/preset-react@npm:7.29.7" @@ -2304,7 +2197,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.16.7, @babel/preset-typescript@npm:^7.23.0": +"@babel/preset-typescript@npm:^7.23.0": version: 7.27.1 resolution: "@babel/preset-typescript@npm:7.27.1" dependencies: @@ -2341,7 +2234,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.25.0, @babel/template@npm:^7.27.1, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" dependencies: @@ -2352,7 +2245,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.29.7": +"@babel/template@npm:^7.28.6, @babel/template@npm:^7.29.7": version: 7.29.7 resolution: "@babel/template@npm:7.29.7" dependencies: @@ -2363,7 +2256,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": +"@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": version: 7.28.4 resolution: "@babel/traverse@npm:7.28.4" dependencies: @@ -2378,6 +2271,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.29.0": + version: 7.29.8 + resolution: "@babel/traverse@npm:7.29.8" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.8" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.8" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.8" + debug: "npm:^4.3.1" + checksum: 10c0/87a28989c434add26d787776ac6d30f749b89cb030f2a605c89f671a516a6fa165ac0476f07e2b69feed70128b2734cae1cbbf41dfe95ccf22081bd8f8b91923 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.29.7": version: 7.29.7 resolution: "@babel/traverse@npm:7.29.7" @@ -2393,7 +2301,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.2, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.28.4 resolution: "@babel/types@npm:7.28.4" dependencies: @@ -2403,6 +2311,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.29.0, @babel/types@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/be7c279f0abf2a086c633e21b49c7ca80275d05283cc5a268b67a708c9914bd0c944f1422b3eb3cb37682a2af5d560abf520ccf9b01b53ecbfe6b71fbc3fdde6 + languageName: node + linkType: hard + "@babel/types@npm:^7.29.7": version: 7.29.7 resolution: "@babel/types@npm:7.29.7" @@ -2784,36 +2702,36 @@ __metadata: languageName: node linkType: hard -"@expo/cli@npm:54.0.8": - version: 54.0.8 - resolution: "@expo/cli@npm:54.0.8" - dependencies: - "@0no-co/graphql.web": "npm:^1.0.8" - "@expo/code-signing-certificates": "npm:^0.0.5" - "@expo/config": "npm:~12.0.9" - "@expo/config-plugins": "npm:~54.0.1" - "@expo/devcert": "npm:^1.1.2" - "@expo/env": "npm:~2.0.7" - "@expo/image-utils": "npm:^0.8.7" - "@expo/json-file": "npm:^10.0.7" - "@expo/mcp-tunnel": "npm:~0.0.7" - "@expo/metro": "npm:~54.0.0" - "@expo/metro-config": "npm:~54.0.5" - "@expo/osascript": "npm:^2.3.7" - "@expo/package-manager": "npm:^1.9.8" - "@expo/plist": "npm:^0.4.7" - "@expo/prebuild-config": "npm:^54.0.3" - "@expo/schema-utils": "npm:^0.1.7" - "@expo/server": "npm:^0.7.5" - "@expo/spawn-async": "npm:^1.7.2" - "@expo/ws-tunnel": "npm:^1.0.1" - "@expo/xcpretty": "npm:^4.3.0" - "@react-native/dev-middleware": "npm:0.81.4" - "@urql/core": "npm:^5.0.6" - "@urql/exchange-retry": "npm:^1.3.0" +"@expo/cli@npm:^57.0.24": + version: 57.0.24 + resolution: "@expo/cli@npm:57.0.24" + dependencies: + "@expo/code-signing-certificates": "npm:^0.0.6" + "@expo/config": "npm:~57.0.9" + "@expo/config-plugins": "npm:~57.0.9" + "@expo/devcert": "npm:^1.2.1" + "@expo/env": "npm:~2.4.3" + "@expo/image-utils": "npm:^0.11.5" + "@expo/inline-modules": "npm:^0.1.7" + "@expo/json-file": "npm:^11.0.1" + "@expo/log-box": "npm:^57.0.4" + "@expo/metro": "npm:~56.0.2" + "@expo/metro-config": "npm:~57.0.12" + "@expo/metro-file-map": "npm:^57.0.3" + "@expo/osascript": "npm:^2.7.1" + "@expo/package-manager": "npm:^1.13.1" + "@expo/plist": "npm:^0.8.1" + "@expo/prebuild-config": "npm:^57.0.16" + "@expo/require-utils": "npm:^57.0.5" + "@expo/router-server": "npm:^57.0.9" + "@expo/schema-utils": "npm:^57.0.2" + "@expo/spawn-async": "npm:^1.8.0" + "@expo/ws-tunnel": "npm:^2.0.0" + "@expo/xcpretty": "npm:^4.4.4" + "@react-native/dev-middleware": "npm:0.86.3" accepts: "npm:^1.3.8" + agent-cli-detector: "npm:0.1.7" arg: "npm:^5.0.2" - better-opn: "npm:~3.0.2" bplist-creator: "npm:0.1.0" bplist-parser: "npm:^0.3.1" chalk: "npm:^4.0.0" @@ -2821,37 +2739,32 @@ __metadata: compression: "npm:^1.7.4" connect: "npm:^3.7.0" debug: "npm:^4.3.4" - env-editor: "npm:^0.4.1" - freeport-async: "npm:^2.0.0" + dnssd-advertise: "npm:^1.1.4" + expo-server: "npm:^57.0.3" + fetch-nodeshim: "npm:^0.4.10" getenv: "npm:^2.0.0" - glob: "npm:^10.4.2" - lan-network: "npm:^0.1.6" - minimatch: "npm:^9.0.0" - node-forge: "npm:^1.3.1" + glob: "npm:^13.0.0" + lan-network: "npm:^0.2.1" + multitars: "npm:^1.0.2" + node-forge: "npm:^1.3.3" npm-package-arg: "npm:^11.0.0" ora: "npm:^3.4.0" - picomatch: "npm:^3.0.1" - pretty-bytes: "npm:^5.6.0" + picomatch: "npm:^4.0.4" pretty-format: "npm:^29.7.0" progress: "npm:^2.0.3" prompts: "npm:^2.3.2" - qrcode-terminal: "npm:0.11.0" - require-from-string: "npm:^2.0.2" - requireg: "npm:^0.2.2" - resolve: "npm:^1.22.2" resolve-from: "npm:^5.0.0" - resolve.exports: "npm:^2.0.3" + sandbox-cli-detector: "npm:^0.2.0" semver: "npm:^7.6.0" send: "npm:^0.19.0" slugify: "npm:^1.3.4" - source-map-support: "npm:~0.5.21" stacktrace-parser: "npm:^0.1.10" structured-headers: "npm:^0.4.1" - tar: "npm:^7.4.3" terminal-link: "npm:^2.1.1" - undici: "npm:^6.18.2" + toqr: "npm:^0.1.1" wrap-ansi: "npm:^7.0.0" ws: "npm:^8.12.1" + zod: "npm:^3.25.76" peerDependencies: expo: "*" expo-router: "*" @@ -2862,18 +2775,17 @@ __metadata: react-native: optional: true bin: - expo-internal: build/bin/cli - checksum: 10c0/702190faef4eee8e8272743a55755c1cfb6d63de8db2a988f2466e253156919d5cfc8ee43fa06bc35f4be797cc9235f98ddcc099e75d913f793edaca866f4dd5 + expo-internal: main.js + checksum: 10c0/efcafad4ddd4ca7a3b64d4c1945bcc93b1c1905cb08bf9b72d2eaf70c50c2ec463ed2c75f6270949385797d14d4adf7f08150db2428795e7a4923d1508486ce8 languageName: node linkType: hard -"@expo/code-signing-certificates@npm:^0.0.5": - version: 0.0.5 - resolution: "@expo/code-signing-certificates@npm:0.0.5" +"@expo/code-signing-certificates@npm:^0.0.6": + version: 0.0.6 + resolution: "@expo/code-signing-certificates@npm:0.0.6" dependencies: - node-forge: "npm:^1.2.1" - nullthrows: "npm:^1.1.1" - checksum: 10c0/98c908c54f92d6782ae01fef47dd858140dc6013e5376ee3faf9b243327f2b16279441fec171cbde45d0e3ebd0bf72db57b4d4c2a0c4f952285b0b377b2b356b + node-forge: "npm:^1.3.3" + checksum: 10c0/3c60be55fb056ccebf7355c1dbe959cee191eaa1c33c6ff5a7331c1ffe1cfa66edc6b62e8005b4a9023bbd40462d81d35284e79eaa8893facb2493801685bbea languageName: node linkType: hard @@ -2899,25 +2811,24 @@ __metadata: languageName: node linkType: hard -"@expo/config-plugins@npm:~54.0.1": - version: 54.0.1 - resolution: "@expo/config-plugins@npm:54.0.1" +"@expo/config-plugins@npm:~57.0.9": + version: 57.0.9 + resolution: "@expo/config-plugins@npm:57.0.9" dependencies: - "@expo/config-types": "npm:^54.0.8" - "@expo/json-file": "npm:~10.0.7" - "@expo/plist": "npm:^0.4.7" + "@expo/config-types": "npm:^57.0.2" + "@expo/json-file": "npm:~11.0.1" + "@expo/plist": "npm:^0.8.1" + "@expo/require-utils": "npm:^57.0.5" "@expo/sdk-runtime-versions": "npm:^1.0.0" chalk: "npm:^4.1.2" debug: "npm:^4.3.5" getenv: "npm:^2.0.0" - glob: "npm:^10.4.2" - resolve-from: "npm:^5.0.0" + glob: "npm:^13.0.0" semver: "npm:^7.5.4" - slash: "npm:^3.0.0" slugify: "npm:^1.6.6" xcode: "npm:^3.0.1" xml2js: "npm:0.6.0" - checksum: 10c0/43ff5ffaf4860e83fda84a583a3a68469a7a1aa7263508500ec839363874290d3043f36d4bc90b77b93188439e88e40d6ac22a76f2146fd4f231cfe4a179e6da + checksum: 10c0/a980ceea445860797dd8d5f446b9dd1710b4101a0d9ac362157c9af2f5777ed7365c09c13f2fd42c00252060105e92e44a390841c294d52dbcb0362cac21c5c0 languageName: node linkType: hard @@ -2928,10 +2839,10 @@ __metadata: languageName: node linkType: hard -"@expo/config-types@npm:^54.0.8": - version: 54.0.8 - resolution: "@expo/config-types@npm:54.0.8" - checksum: 10c0/8ea03fe4b18277b76d40bcd4d64a247013a2a24669e021f489f5b5a4abc06b753a9545d072c3eb12b8946cc51ad99f1c92734b94fef2c151a493a1ca78bdbf84 +"@expo/config-types@npm:^57.0.2": + version: 57.0.2 + resolution: "@expo/config-types@npm:57.0.2" + checksum: 10c0/3e3a8abcab03791dfccc758bde5ce85f0fa4b14c22df072e6cc72c670945fc65433239bf2ce39f86bbda422b338d19323612d61eabb2442f988713214837a6dc languageName: node linkType: hard @@ -2956,41 +2867,37 @@ __metadata: languageName: node linkType: hard -"@expo/config@npm:~12.0.9": - version: 12.0.9 - resolution: "@expo/config@npm:12.0.9" +"@expo/config@npm:~57.0.9": + version: 57.0.9 + resolution: "@expo/config@npm:57.0.9" dependencies: - "@babel/code-frame": "npm:~7.10.4" - "@expo/config-plugins": "npm:~54.0.1" - "@expo/config-types": "npm:^54.0.8" - "@expo/json-file": "npm:^10.0.7" + "@expo/config-plugins": "npm:~57.0.9" + "@expo/config-types": "npm:^57.0.2" + "@expo/json-file": "npm:^11.0.1" + "@expo/require-utils": "npm:^57.0.5" deepmerge: "npm:^4.3.1" getenv: "npm:^2.0.0" - glob: "npm:^10.4.2" - require-from-string: "npm:^2.0.2" - resolve-from: "npm:^5.0.0" + glob: "npm:^13.0.0" resolve-workspace-root: "npm:^2.0.0" semver: "npm:^7.6.0" slugify: "npm:^1.3.4" - sucrase: "npm:3.35.0" - checksum: 10c0/ab2cb75e8c973d82a5dbffda3c333c216f1765d5757ab3ca95d8f1ebc31b1e956deb0b7a1a181ae82a8f73fdfef4dea4415371ad3df82e95b1c127133c4363c9 + checksum: 10c0/b70700a5bdf405c7b71878f5c4549f034a0080ba427069089326017e5b777cbdddaf9abf094aa0de83c80202fea479078fe452ae3ac11c3b75ca7922376ac409 languageName: node linkType: hard -"@expo/devcert@npm:^1.1.2": - version: 1.2.0 - resolution: "@expo/devcert@npm:1.2.0" +"@expo/devcert@npm:^1.2.1": + version: 1.2.1 + resolution: "@expo/devcert@npm:1.2.1" dependencies: "@expo/sudo-prompt": "npm:^9.3.1" debug: "npm:^3.1.0" - glob: "npm:^10.4.2" - checksum: 10c0/3d6a1ce44918c2e5be3bb89d25cfc80551623e4fe5004d4eb29d1edc8edd676258345e64d2aefe56188bc5d4b33e2b7e733a108b2be225af1f90ca86d7170069 + checksum: 10c0/7c5cb4fa74a14702a44b4772a56f27fd191b6cd08988f3da01323f6d592623c80247171b7d66b2c0a32408f48a0814162dbb2764042444887f27e38b89ad1051 languageName: node linkType: hard -"@expo/devtools@npm:0.1.7": - version: 0.1.7 - resolution: "@expo/devtools@npm:0.1.7" +"@expo/devtools@npm:~57.0.1": + version: 57.0.1 + resolution: "@expo/devtools@npm:57.0.1" dependencies: chalk: "npm:^4.1.2" peerDependencies: @@ -3001,69 +2908,91 @@ __metadata: optional: true react-native: optional: true - checksum: 10c0/4525a007db0b3c89d7e0400f8ec9ede679d0ee110e572c9ba12e8430d564b7c9967031b0f316f0379bb5310b9b1b78adccd659cfca904effaa27706d15325fe8 + checksum: 10c0/bf3a24db47725ebeb79adbb95c3cddb71ea26e0040e80ef9aeb50b538417581765617a8fa6c5c63b78cb1d633548da8fc926d892331515e8fb180e99831f0156 languageName: node linkType: hard -"@expo/env@npm:~2.0.7": - version: 2.0.7 - resolution: "@expo/env@npm:2.0.7" +"@expo/dom-webview@npm:^57.0.1, @expo/dom-webview@npm:~57.0.1": + version: 57.0.1 + resolution: "@expo/dom-webview@npm:57.0.1" + peerDependencies: + expo: "*" + react: "*" + react-native: "*" + checksum: 10c0/22c2868e9c6c2eea2f607b1f9b4afc966b4fba3a03a6b05cabe9c8c51540fae4252f60ff171ce406ebcccca8b3e5931d07d0dd04c777020669a1dd11d5cca8a6 + languageName: node + linkType: hard + +"@expo/env@npm:^2.4.3, @expo/env@npm:~2.4.3": + version: 2.4.3 + resolution: "@expo/env@npm:2.4.3" dependencies: chalk: "npm:^4.0.0" debug: "npm:^4.3.4" - dotenv: "npm:~16.4.5" - dotenv-expand: "npm:~11.0.6" getenv: "npm:^2.0.0" - checksum: 10c0/029914cfca2f85dd2c159b7331492d26255db7cf1615d02108041f561f3e7286d77f473943dd1d6ede68ee3cea8b63271c8352fa9b728c90e020f50d6d8c5582 + checksum: 10c0/36eb2dacda0765eca69b3df7aaa3e5a74fb05561f5e223ca19a376a70ed18934edb1782345299fb9b026f0d1e4773f047835f1c16035151c1a76a1e373b29412 + languageName: node + linkType: hard + +"@expo/expo-modules-macros-plugin@npm:0.6.1": + version: 0.6.1 + resolution: "@expo/expo-modules-macros-plugin@npm:0.6.1" + checksum: 10c0/1cad524b3468650f1504c09f300b2be2f469b2e997788d7d6a443a1d13944bae679f6976fc1d13d4ffa485ccc5ddb562919dca79aa1d3b46e6adaa78cb787842 languageName: node linkType: hard -"@expo/fingerprint@npm:0.15.1": - version: 0.15.1 - resolution: "@expo/fingerprint@npm:0.15.1" +"@expo/fingerprint@npm:^0.20.13": + version: 0.20.13 + resolution: "@expo/fingerprint@npm:0.20.13" dependencies: - "@expo/spawn-async": "npm:^1.7.2" + "@expo/env": "npm:^2.4.3" + "@expo/spawn-async": "npm:^1.8.0" arg: "npm:^5.0.2" chalk: "npm:^4.1.2" debug: "npm:^4.3.4" getenv: "npm:^2.0.0" - glob: "npm:^10.4.2" + glob: "npm:^13.0.0" ignore: "npm:^5.3.1" - minimatch: "npm:^9.0.0" - p-limit: "npm:^3.1.0" + minimatch: "npm:^10.2.2" resolve-from: "npm:^5.0.0" semver: "npm:^7.6.0" bin: fingerprint: bin/cli.js - checksum: 10c0/3c1fc09e59b8ab984aed780d6d06411c00a6755e7d606f127e6c6e869249a4d0d69f26259fd3347abe9cbf1b658105895703039813f07df01d95876db7bc468b + checksum: 10c0/0fc278d17080b5855db1d0afd3b6d5d4c8eaccbe53542c9d8fc5c7e385b43b03c4445ba11d085fc8c86308347efb774d75a9c0201973e7b0d8a6823af36e1159 languageName: node linkType: hard -"@expo/image-utils@npm:^0.8.7": - version: 0.8.7 - resolution: "@expo/image-utils@npm:0.8.7" +"@expo/image-utils@npm:^0.11.5": + version: 0.11.5 + resolution: "@expo/image-utils@npm:0.11.5" dependencies: - "@expo/spawn-async": "npm:^1.7.2" + "@expo/require-utils": "npm:^57.0.5" + "@expo/spawn-async": "npm:^1.8.0" chalk: "npm:^4.0.0" getenv: "npm:^2.0.0" jimp-compact: "npm:0.16.1" parse-png: "npm:^2.1.0" - resolve-from: "npm:^5.0.0" - resolve-global: "npm:^1.0.0" semver: "npm:^7.6.0" - temp-dir: "npm:~2.0.0" - unique-string: "npm:~2.0.0" - checksum: 10c0/763fbe6d5e34c6e40b74f1088dd5a3bf8cf54a57b073ec55230a8c2f87deb9204a79ce3750d40745c330cb438e50d854d0177a7f7d19c2055770687ef243660e + checksum: 10c0/bca05d61164c88eafcbb01e82eea92679b9fcbc435ad50f0b8f7b0c74259ea50a7e7535d5403ea747be241c9560b04be66df8f3e0f926a6da1e5a1954a41a45d + languageName: node + linkType: hard + +"@expo/inline-modules@npm:^0.1.7": + version: 0.1.7 + resolution: "@expo/inline-modules@npm:0.1.7" + dependencies: + "@expo/config-plugins": "npm:~57.0.9" + checksum: 10c0/3dff95ce7d61915a20687cf4d3112acfa1c5865d2ca6fa814783355fb4220f074344718c81ec319849f3d9a8e2b942ca540ba7ed1a4377af844b7e6637da2191 languageName: node linkType: hard -"@expo/json-file@npm:^10.0.7, @expo/json-file@npm:~10.0.7": - version: 10.0.7 - resolution: "@expo/json-file@npm:10.0.7" +"@expo/json-file@npm:^11.0.1, @expo/json-file@npm:~11.0.1": + version: 11.0.1 + resolution: "@expo/json-file@npm:11.0.1" dependencies: - "@babel/code-frame": "npm:~7.10.4" + "@babel/code-frame": "npm:^7.20.0" json5: "npm:^2.2.3" - checksum: 10c0/3dfff7fe435d286f6c2f55569a8667f6d52133fc96a263e7421fa49cbf2ad7a4e2952da1fa7a3cdb15c52f11e891335ec784d358c3be554f966fdf5c836cc944 + checksum: 10c0/b93e8bf0654c7d417bf00a418932a3f676bc24bed75ff63a8c683229e2d1f3b7a48206fc3af7db307ed93a776adab51cb62ebb6a0ed77f86067c7ae85cc2f74d languageName: node linkType: hard @@ -3077,65 +3006,93 @@ __metadata: languageName: node linkType: hard -"@expo/mcp-tunnel@npm:~0.0.7": - version: 0.0.8 - resolution: "@expo/mcp-tunnel@npm:0.0.8" +"@expo/local-build-cache-provider@npm:^57.0.8": + version: 57.0.8 + resolution: "@expo/local-build-cache-provider@npm:57.0.8" dependencies: - ws: "npm:^8.18.3" - zod: "npm:^3.25.76" - zod-to-json-schema: "npm:^3.24.6" + "@expo/config": "npm:~57.0.9" + chalk: "npm:^4.1.2" + checksum: 10c0/9750ae6dbf91a0fc5eb3ffef7f2974c1193a9c212e5cbf80fee78d777c1545cf0445544ea7bc61f0f77767dea7c40b263643caf0d75e54dc84aa9139eaa40795 + languageName: node + linkType: hard + +"@expo/log-box@npm:^57.0.4": + version: 57.0.4 + resolution: "@expo/log-box@npm:57.0.4" + dependencies: + "@expo/dom-webview": "npm:^57.0.1" + anser: "npm:^1.4.9" + stacktrace-parser: "npm:^0.1.10" peerDependencies: - "@modelcontextprotocol/sdk": ^1.13.2 - peerDependenciesMeta: - "@modelcontextprotocol/sdk": - optional: true - checksum: 10c0/47588cd3944c21be5a5364bf8f30f994bb1a667b25ebbc863273d1895a6e1cbf5a82996d9101b487700bc7a9a9730ee81c60f8132523ed38176c542aa9b85521 + "@expo/dom-webview": ^57.0.1 + expo: "*" + react: "*" + react-native: "*" + checksum: 10c0/750b3482d657f450d545130ffe8c3b4e8f860561ad55f75f3058d1e08e0b37c16d512470a52d33cac1d459bbfb695784b199acd15134ec4309bc67287d88709f languageName: node linkType: hard -"@expo/metro-config@npm:54.0.5, @expo/metro-config@npm:~54.0.5": - version: 54.0.5 - resolution: "@expo/metro-config@npm:54.0.5" +"@expo/metro-config@npm:57.0.12, @expo/metro-config@npm:~57.0.12": + version: 57.0.12 + resolution: "@expo/metro-config@npm:57.0.12" dependencies: "@babel/code-frame": "npm:^7.20.0" "@babel/core": "npm:^7.20.0" "@babel/generator": "npm:^7.20.5" - "@expo/config": "npm:~12.0.9" - "@expo/env": "npm:~2.0.7" - "@expo/json-file": "npm:~10.0.7" - "@expo/metro": "npm:~54.0.0" - "@expo/spawn-async": "npm:^1.7.2" + "@expo/config": "npm:~57.0.9" + "@expo/env": "npm:~2.4.3" + "@expo/json-file": "npm:~11.0.1" + "@expo/metro": "npm:~56.0.2" + "@expo/require-utils": "npm:^57.0.5" + "@expo/spawn-async": "npm:^1.8.0" + "@jridgewell/gen-mapping": "npm:^0.3.13" + "@jridgewell/remapping": "npm:^2.3.5" + "@jridgewell/sourcemap-codec": "npm:^1.5.5" browserslist: "npm:^4.25.0" chalk: "npm:^4.1.0" debug: "npm:^4.3.2" - dotenv: "npm:~16.4.5" - dotenv-expand: "npm:~11.0.6" getenv: "npm:^2.0.0" - glob: "npm:^10.4.2" - hermes-parser: "npm:^0.29.1" + glob: "npm:^13.0.0" + hermes-parser: "npm:^0.36.0" jsc-safe-url: "npm:^0.2.4" lightningcss: "npm:^1.30.1" - minimatch: "npm:^9.0.0" - postcss: "npm:~8.4.32" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.14" resolve-from: "npm:^5.0.0" peerDependencies: expo: "*" peerDependenciesMeta: expo: optional: true - checksum: 10c0/13d588dcce5a48b5dd0034bcff44cb539720dae5937f56b780bc79b3bb56f34fba81b1b736f0cc0512e9f83d29d39e907400c449902a57d0d75db9ee447edb19 + checksum: 10c0/eeb34426026d7b1188ba528c4e2c3c1f1023d9a2b0af192b120c6bcf0c89aa9d2c9c6af1086a02a05444a21a5eac4b2bdd89e11288abceacd7debd380edd864a languageName: node linkType: hard -"@expo/metro-runtime@npm:~6.1.2": - version: 6.1.2 - resolution: "@expo/metro-runtime@npm:6.1.2" +"@expo/metro-file-map@npm:^57.0.3": + version: 57.0.3 + resolution: "@expo/metro-file-map@npm:57.0.3" dependencies: + debug: "npm:^4.3.4" + fb-watchman: "npm:^2.0.2" + invariant: "npm:^2.2.4" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + checksum: 10c0/57770deb2f83b21418ef86a9484521d27a66c20156a7aed33fc72a035d2b445b81bc9a10f464fb691a934cecf95da1fd30b759b09eee1bb6818f244d7d92d453 + languageName: node + linkType: hard + +"@expo/metro-runtime@npm:~57.0.15": + version: 57.0.15 + resolution: "@expo/metro-runtime@npm:57.0.15" + dependencies: + "@expo/log-box": "npm:^57.0.4" anser: "npm:^1.4.9" pretty-format: "npm:^29.7.0" stacktrace-parser: "npm:^0.1.10" whatwg-fetch: "npm:^3.0.0" peerDependencies: + "@expo/log-box": ^57.0.4 expo: "*" react: "*" react-dom: "*" @@ -3143,51 +3100,52 @@ __metadata: peerDependenciesMeta: react-dom: optional: true - checksum: 10c0/8cc8fa526f5718449dfd256331db222cd48f5d18cbd5a4156205d84111dc40519b700392ee2c9ee99efedc14fa793d17e51d0d9c33b0cc60869d016c00acc7d1 + checksum: 10c0/1408d84e7dbf21ee164b84aca3e3419c0c9301846226d6c6e76a4b33f693ad064aa638686f42752b22901141d875505f0a110cc576756e018ce7e4e20a77a8e9 languageName: node linkType: hard -"@expo/metro@npm:~54.0.0": - version: 54.0.0 - resolution: "@expo/metro@npm:54.0.0" +"@expo/metro@npm:~56.0.2": + version: 56.0.2 + resolution: "@expo/metro@npm:56.0.2" dependencies: - metro: "npm:0.83.1" - metro-babel-transformer: "npm:0.83.1" - metro-cache: "npm:0.83.1" - metro-cache-key: "npm:0.83.1" - metro-config: "npm:0.83.1" - metro-core: "npm:0.83.1" - metro-file-map: "npm:0.83.1" - metro-resolver: "npm:0.83.1" - metro-runtime: "npm:0.83.1" - metro-source-map: "npm:0.83.1" - metro-transform-plugins: "npm:0.83.1" - metro-transform-worker: "npm:0.83.1" - checksum: 10c0/2652c0e5c8a474e4a50e2d6b45ba66f25824c8c58f3c79125818c815984f5a7e78e114ce20e00846209fb98e51e2192c64fffc64a3a19b75bd583b51481e8083 + metro: "npm:0.84.5" + metro-babel-transformer: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-cache-key: "npm:0.84.5" + metro-config: "npm:0.84.5" + metro-core: "npm:0.84.5" + metro-file-map: "npm:0.84.5" + metro-minify-terser: "npm:0.84.5" + metro-resolver: "npm:0.84.5" + metro-runtime: "npm:0.84.5" + metro-source-map: "npm:0.84.5" + metro-symbolicate: "npm:0.84.5" + metro-transform-plugins: "npm:0.84.5" + metro-transform-worker: "npm:0.84.5" + checksum: 10c0/5cf6b2ffe125d6d54138757e8fada0d88515ee0ec2113595ca224babf8baf95b866e12eba51cdb1d89aae4476a7982a653e46ea2091b22e1433984ae608cc380 languageName: node linkType: hard -"@expo/osascript@npm:^2.3.7": - version: 2.3.7 - resolution: "@expo/osascript@npm:2.3.7" +"@expo/osascript@npm:^2.7.1": + version: 2.7.1 + resolution: "@expo/osascript@npm:2.7.1" dependencies: - "@expo/spawn-async": "npm:^1.7.2" - exec-async: "npm:^2.2.0" - checksum: 10c0/7778120019f3969e68e2473d8a75e35b03e1b8f573a6d306603b9007953595b28ef042eaf16580f00c48b063fdc808f7a8a6cfd302fedcbe149bd1a3e44c84c9 + "@expo/spawn-async": "npm:^1.8.0" + checksum: 10c0/45223563531d300e4e7d7035278ea1a1ffc7410dbfa5a1f6297847732061ee971f92a89d8f733cdfe306eaddc43f288fe3231a3a9bb9e66a226bc4ebd990bf2c languageName: node linkType: hard -"@expo/package-manager@npm:^1.9.8": - version: 1.9.8 - resolution: "@expo/package-manager@npm:1.9.8" +"@expo/package-manager@npm:^1.13.1": + version: 1.13.1 + resolution: "@expo/package-manager@npm:1.13.1" dependencies: - "@expo/json-file": "npm:^10.0.7" - "@expo/spawn-async": "npm:^1.7.2" + "@expo/json-file": "npm:^11.0.1" + "@expo/spawn-async": "npm:^1.8.0" chalk: "npm:^4.0.0" npm-package-arg: "npm:^11.0.0" ora: "npm:^3.4.0" resolve-workspace-root: "npm:^2.0.0" - checksum: 10c0/d9f727a9b02a13d7fac8afccc5608f46690ca3e27f834042e74bc271607a5a085ad4600a19eeda55556251b8f1f0960cf71609680df7fedf76750141cb0d2a9e + checksum: 10c0/a9e24519c85046ca159d944d1051647159f96165fb423bf1f9d02c32abe95a46a81b7c8a2ce7f012bb84c5baf1a71500f07f6c57422b95d079dfb4f58d7e9e75 languageName: node linkType: hard @@ -3202,41 +3160,83 @@ __metadata: languageName: node linkType: hard -"@expo/plist@npm:^0.4.7": - version: 0.4.7 - resolution: "@expo/plist@npm:0.4.7" +"@expo/plist@npm:^0.8.1": + version: 0.8.1 + resolution: "@expo/plist@npm:0.8.1" dependencies: "@xmldom/xmldom": "npm:^0.8.8" - base64-js: "npm:^1.2.3" + base64-js: "npm:^1.5.1" xmlbuilder: "npm:^15.1.1" - checksum: 10c0/697b3845e7898516de4d25ac28ae9fcbf1e58612bd543b41e11b3d41b9a52b754f2775dc2891cd3068a88f0375a77c2805073c12c5d54f3cd7ef6d315d0a3b92 + checksum: 10c0/45f130f3d5155d46dc415ab61b97449d5cc7bf47e41ed37965f12e50cc9728aa7c4da83fc958a8d7113e31e4342baebc1b2517de7e20186f3e64d53be378a9ce languageName: node linkType: hard -"@expo/prebuild-config@npm:^54.0.3": - version: 54.0.3 - resolution: "@expo/prebuild-config@npm:54.0.3" +"@expo/prebuild-config@npm:^57.0.16": + version: 57.0.16 + resolution: "@expo/prebuild-config@npm:57.0.16" dependencies: - "@expo/config": "npm:~12.0.9" - "@expo/config-plugins": "npm:~54.0.1" - "@expo/config-types": "npm:^54.0.8" - "@expo/image-utils": "npm:^0.8.7" - "@expo/json-file": "npm:^10.0.7" - "@react-native/normalize-colors": "npm:0.81.4" + "@expo/config": "npm:~57.0.9" + "@expo/config-plugins": "npm:~57.0.9" + "@expo/config-types": "npm:^57.0.2" + "@expo/image-utils": "npm:^0.11.5" + "@expo/json-file": "npm:^11.0.1" + "@react-native/normalize-colors": "npm:0.86.3" debug: "npm:^4.3.1" + expo-modules-autolinking: "npm:~57.0.13" resolve-from: "npm:^5.0.0" semver: "npm:^7.6.0" - xml2js: "npm:0.6.0" + checksum: 10c0/3c57618fcde5bc7cbbb55b20ff130e7b64f098668a904434b17d9471c69a7df26c73429cbd51e504961d658882da82db41e03631ccbd5df1d538584c0e079a99 + languageName: node + linkType: hard + +"@expo/require-utils@npm:^57.0.5": + version: 57.0.5 + resolution: "@expo/require-utils@npm:57.0.5" + dependencies: + "@babel/code-frame": "npm:^7.20.0" + "@babel/core": "npm:^7.25.2" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + peerDependencies: + typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/42841b76d6540098b664bee633e6ef9825ac84041e7c91045bd15a84d6aed4f2ad1f5027da263755a1e2dc2190283e7b7d6c4fb9ec3335c7a87016606b443754 + languageName: node + linkType: hard + +"@expo/router-server@npm:^57.0.9": + version: 57.0.9 + resolution: "@expo/router-server@npm:57.0.9" + dependencies: + debug: "npm:^4.3.4" peerDependencies: + "@expo/metro-runtime": ^57.0.15 expo: "*" - checksum: 10c0/06db75ff6eee788a71a599d40b8633a51b693b3701ea6c8721d09d1fffd17b08e6ef19077d4dea4e11eb3907cc95483981846bc152bbd9b0ef268b014259541e + expo-constants: ^57.0.17 + expo-font: ^57.0.3 + expo-router: "*" + expo-server: ^57.0.3 + react: "*" + react-dom: "*" + react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 + peerDependenciesMeta: + "@expo/metro-runtime": + optional: true + expo-router: + optional: true + react-dom: + optional: true + react-server-dom-webpack: + optional: true + checksum: 10c0/f8a802ea7bcb73df0da718ee895341c0ca9ecdb0dd40a27316b9960524bac0c0bdfd9ee271b536e28ab1e46ec333f3e966548dfcca6ecacc1c21eb7f54e3703b languageName: node linkType: hard -"@expo/schema-utils@npm:^0.1.7": - version: 0.1.7 - resolution: "@expo/schema-utils@npm:0.1.7" - checksum: 10c0/1099bd8801ff941584bc6d2bb44613f9fb87af663843d629d9ede8315f44f7332c881b70f1681e8f8fc82b27472b4a025341963f0f347e16a0ae90fcb65138cd +"@expo/schema-utils@npm:^57.0.2": + version: 57.0.2 + resolution: "@expo/schema-utils@npm:57.0.2" + checksum: 10c0/5438d6dd1cb58c35c1202cefd7bb44f38eb7078da08b706849154bd6d97f0a9db7a41862110e34d7e111edf50ac44ffe132b0af64ebfdcce8203eb8e5ecd51b9 languageName: node linkType: hard @@ -3247,16 +3247,6 @@ __metadata: languageName: node linkType: hard -"@expo/server@npm:^0.7.5": - version: 0.7.5 - resolution: "@expo/server@npm:0.7.5" - dependencies: - abort-controller: "npm:^3.0.0" - debug: "npm:^4.3.4" - checksum: 10c0/bc68378a22d175dd77f78fa6f0b0fd02fe149b56c049e56cb835115bd5f61d4363dc1d3edd85dd4c59940d0b75e704e582bf74a3b043c6af20a44aacc8af0f41 - languageName: node - linkType: hard - "@expo/spawn-async@npm:^1.7.2": version: 1.7.2 resolution: "@expo/spawn-async@npm:1.7.2" @@ -3266,6 +3256,15 @@ __metadata: languageName: node linkType: hard +"@expo/spawn-async@npm:^1.8.0": + version: 1.8.0 + resolution: "@expo/spawn-async@npm:1.8.0" + dependencies: + cross-spawn: "npm:^7.0.6" + checksum: 10c0/08d3c63f9cc097ce9c8cf6850ca482fd7999a6fddc4cb38a3a9915a1662cb674fe7353de2eb3c693728542bf57db732ae433e82b2d698be141d07cea3092ebf3 + languageName: node + linkType: hard + "@expo/sudo-prompt@npm:^9.3.1": version: 9.3.2 resolution: "@expo/sudo-prompt@npm:9.3.2" @@ -3273,35 +3272,25 @@ __metadata: languageName: node linkType: hard -"@expo/vector-icons@npm:^15.0.2": - version: 15.0.2 - resolution: "@expo/vector-icons@npm:15.0.2" +"@expo/ws-tunnel@npm:^2.0.0": + version: 2.0.0 + resolution: "@expo/ws-tunnel@npm:2.0.0" peerDependencies: - expo-font: ">=14.0.4" - react: "*" - react-native: "*" - checksum: 10c0/3a029c722cb3b2d7502ca984f238e5cc8fdd025860d98b3e13c15aafe9d961869f67831a28c84712438413f4922755c27c1ce35e361191fdc3880ef7728c86dc - languageName: node - linkType: hard - -"@expo/ws-tunnel@npm:^1.0.1": - version: 1.0.6 - resolution: "@expo/ws-tunnel@npm:1.0.6" - checksum: 10c0/050eb7fbd54b636c97c818e7ec5402ce616cae655290386a51600b200947e281cdd12d182251c07fab449e11a732135d61429b738cd03945e94757061e652ecd + ws: ^8.0.0 + checksum: 10c0/5668ffcb3525f98339f1eac579267483771788f98a216ecf6d4c4a106c62ed4e4501ab64ded530a14b865f1b014bb7631dd2c243b08b91e425b422cfee9d0fdf languageName: node linkType: hard -"@expo/xcpretty@npm:^4.3.0": - version: 4.3.2 - resolution: "@expo/xcpretty@npm:4.3.2" +"@expo/xcpretty@npm:^4.4.4": + version: 4.4.5 + resolution: "@expo/xcpretty@npm:4.4.5" dependencies: - "@babel/code-frame": "npm:7.10.4" + "@babel/code-frame": "npm:^7.20.0" chalk: "npm:^4.1.0" - find-up: "npm:^5.0.0" js-yaml: "npm:^4.1.0" bin: excpretty: build/cli.js - checksum: 10c0/e524817b2e42fb8c8914fca7e8f7c2f723f4f6d338a57b7ae97cd3e76da8108af63a22d4c7dc2e96a192a248a242f6e0f8056f0ca53bc4fb5cd2e5ae428e0891 + checksum: 10c0/d2f8e02fc665dca5460269b465debc354d57b241376115e504f583c7e42c59a30267c42b969a59b4f5079d2efef88433e0285f3f5d0b5c6760dee1c2daab032e languageName: node linkType: hard @@ -3958,7 +3947,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.13, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": version: 0.3.13 resolution: "@jridgewell/gen-mapping@npm:0.3.13" dependencies: @@ -4218,26 +4207,26 @@ __metadata: languageName: node linkType: hard -"@react-native/assets-registry@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/assets-registry@npm:0.81.4" - checksum: 10c0/4433a354e909344941c7be0de56a6f3b090b2a805f6faf9a35cdf4644f827839399bf074b7836b9742606b470a1dd3442852adeaa0d547748302bf66c7ce64e8 +"@react-native/assets-registry@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/assets-registry@npm:0.86.3" + checksum: 10c0/7971ecc2f7ca8037ed9ae2eb36fd0d9c2f7abb08a14e4a6b79f92d5047c232a963001f516a859e284546cd406dd3dc35bb6d2f6a01a03552b790a2586507df54 languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/babel-plugin-codegen@npm:0.81.4" +"@react-native/babel-plugin-codegen@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/babel-plugin-codegen@npm:0.86.3" dependencies: - "@babel/traverse": "npm:^7.25.3" - "@react-native/codegen": "npm:0.81.4" - checksum: 10c0/620d095c7d3e84658926eab5223edd576991ceb13314e72f67232377fdfca349d2eb35fb758fbde8f362cbd765e31d05f35f399982231cf9f55530d4faeb5573 + "@babel/traverse": "npm:^7.29.0" + "@react-native/codegen": "npm:0.86.3" + checksum: 10c0/e381c2769e05308d85a5705e803387de5a61726c9dd36c48e84e916e790489f65fb8cb0024b257fca54146a03ef62ee96ef14391ea3370f8db77d59b204cb8d2 languageName: node linkType: hard -"@react-native/babel-preset@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/babel-preset@npm:0.81.4" +"@react-native/babel-preset@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/babel-preset@npm:0.86.3" dependencies: "@babel/core": "npm:^7.25.2" "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" @@ -4245,27 +4234,19 @@ __metadata: "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" "@babel/plugin-transform-block-scoping": "npm:^7.25.0" "@babel/plugin-transform-class-properties": "npm:^7.25.4" "@babel/plugin-transform-classes": "npm:^7.25.4" - "@babel/plugin-transform-computed-properties": "npm:^7.24.7" "@babel/plugin-transform-destructuring": "npm:^7.24.8" "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.25.1" - "@babel/plugin-transform-literals": "npm:^7.25.2" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" - "@babel/plugin-transform-parameters": "npm:^7.24.7" "@babel/plugin-transform-private-methods": "npm:^7.24.7" "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" "@babel/plugin-transform-react-display-name": "npm:^7.24.7" @@ -4274,106 +4255,155 @@ __metadata: "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" "@babel/plugin-transform-regenerator": "npm:^7.24.7" "@babel/plugin-transform-runtime": "npm:^7.24.7" - "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" - "@babel/plugin-transform-spread": "npm:^7.24.7" - "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" "@babel/plugin-transform-typescript": "npm:^7.25.2" "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/template": "npm:^7.25.0" - "@react-native/babel-plugin-codegen": "npm:0.81.4" - babel-plugin-syntax-hermes-parser: "npm:0.29.1" + "@react-native/babel-plugin-codegen": "npm:0.86.3" + babel-plugin-syntax-hermes-parser: "npm:0.36.0" babel-plugin-transform-flow-enums: "npm:^0.0.2" react-refresh: "npm:^0.14.0" peerDependencies: "@babel/core": "*" - checksum: 10c0/c20b40c7cd9f72cb6768ccfdc875719d6e601cb9b9dfb6d875733ae65260a108c1661456656135bde1808fad59b7d28266b3a533f18c98a346dc160ecac8bf55 + checksum: 10c0/6c71e9e8c5d3dca9cd17b3371bcfad782c64c9924d56072abf9cb2eb371e642537c72574b7726f9295e6d112472e66c417c8170daf18a1bec77caa03e96b996f languageName: node linkType: hard -"@react-native/codegen@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/codegen@npm:0.81.4" +"@react-native/codegen@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/codegen@npm:0.86.3" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/parser": "npm:^7.25.3" - glob: "npm:^7.1.1" - hermes-parser: "npm:0.29.1" + "@babel/parser": "npm:^7.29.0" + hermes-parser: "npm:0.36.0" invariant: "npm:^2.2.4" nullthrows: "npm:^1.1.1" + tinyglobby: "npm:^0.2.15" yargs: "npm:^17.6.2" peerDependencies: "@babel/core": "*" - checksum: 10c0/6cb89b8ab0c296641633b4f83f9cc7a0d19a6d381312675ca079b33dee746022142ce117ebab69b03e91ff3f0856becae1782d47ae5d4169466e1fe353f5916b + checksum: 10c0/2dc1f49b342d009c2efd56d6d2839fdffac4afefba2e0f2159c8491ce48aef9eb60a0cec2fa88569f956eabd6915f47a5b27ad1cf43463a55e67b213edb84fe7 languageName: node linkType: hard -"@react-native/community-cli-plugin@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/community-cli-plugin@npm:0.81.4" +"@react-native/community-cli-plugin@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/community-cli-plugin@npm:0.86.3" dependencies: - "@react-native/dev-middleware": "npm:0.81.4" + "@react-native/dev-middleware": "npm:0.86.3" debug: "npm:^4.4.0" invariant: "npm:^2.2.4" - metro: "npm:^0.83.1" - metro-config: "npm:^0.83.1" - metro-core: "npm:^0.83.1" + metro: "npm:^0.84.3" + metro-config: "npm:^0.84.3" + metro-core: "npm:^0.84.3" semver: "npm:^7.1.3" peerDependencies: "@react-native-community/cli": "*" - "@react-native/metro-config": "*" + "@react-native/metro-config": 0.86.3 peerDependenciesMeta: "@react-native-community/cli": optional: true "@react-native/metro-config": optional: true - checksum: 10c0/2d3a8eb749f9347603b05f0902ec91409dc06dbeb3f9d94f0669fc41b822a70a042391c0455fce772baa2ec30685e130d36f6945d78b4cd81af905f25701b045 + checksum: 10c0/3fe9b5eef5e35a3c89f5659f9fd7c4429844c82c442f252d03195d60db67515a81deb2633711b8b4070cd479685e7dc6d62c4d2a7c4ef130b4ae17e2a00bc22f languageName: node linkType: hard -"@react-native/debugger-frontend@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/debugger-frontend@npm:0.81.4" - checksum: 10c0/80ebb17b5fba77419ef8f32bc710f614a9dab39ee0e57e8a308f0f2c177aa11595922bc8d5230aca8e33e151334bd6c709f21c7d27e05e656ad484ea308b388a +"@react-native/debugger-frontend@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/debugger-frontend@npm:0.86.3" + checksum: 10c0/377e3cb20097d6f298513f2ab4a98581c212cf7b54e50df85384bc5e6ae73a70f97d4e27047658abe60f39d3ab533e8904033678be732aff52a66355c7c8101d languageName: node linkType: hard -"@react-native/dev-middleware@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/dev-middleware@npm:0.81.4" +"@react-native/debugger-shell@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/debugger-shell@npm:0.86.3" + dependencies: + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.4.0" + fb-dotslash: "npm:0.5.8" + checksum: 10c0/f6c0c7f15e9f9aed247d96be3bca5768f94a65cace549b62bf899c72e0aaff5a564b7898f3f63dc5e987728d4b73db2f786c4373e64272b029a43fef64153395 + languageName: node + linkType: hard + +"@react-native/dev-middleware@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/dev-middleware@npm:0.86.3" dependencies: "@isaacs/ttlcache": "npm:^1.4.1" - "@react-native/debugger-frontend": "npm:0.81.4" + "@react-native/debugger-frontend": "npm:0.86.3" + "@react-native/debugger-shell": "npm:0.86.3" chrome-launcher: "npm:^0.15.2" - chromium-edge-launcher: "npm:^0.2.0" + chromium-edge-launcher: "npm:^0.3.0" connect: "npm:^3.6.5" debug: "npm:^4.4.0" invariant: "npm:^2.2.4" nullthrows: "npm:^1.1.1" open: "npm:^7.0.3" serve-static: "npm:^1.16.2" - ws: "npm:^6.2.3" - checksum: 10c0/d0d2b56cc8701fe3527a0115d810b574af70d57a1330b1bde890f2392a7918acb64be26958e7be48e44fc8635b5dea53de30e8c2d0f6c4222b974cd049a4f3c6 + ws: "npm:^7.5.10" + checksum: 10c0/d313ed01d6e8e7ddff84fee8285a23138863486af02466fb7431891928cab3c51d90859ac883f64cd25051b90bba609990224e9f8d31e8be474f8355deec631d + languageName: node + linkType: hard + +"@react-native/gradle-plugin@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/gradle-plugin@npm:0.86.3" + checksum: 10c0/ebbf2959e99990e97eb16892e6e9b7d9115b8710ce7759a33e7fe3b58610e451841ef4fa58b4820230680dd9af4cee6a39916952f2dfc5463396a4aad80f88f6 languageName: node linkType: hard -"@react-native/gradle-plugin@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/gradle-plugin@npm:0.81.4" - checksum: 10c0/f9cca8439009ea1edf077ac0dfe509cae614dfdbf645ad42b66bed300c8b1595c9dd9fffe04f2a6540cdc2b6119e253bd09267d4a748ed8084cb7221b1de9bdb +"@react-native/jest-preset@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/jest-preset@npm:0.86.3" + dependencies: + "@jest/create-cache-key-function": "npm:^29.7.0" + "@react-native/js-polyfills": "npm:0.86.3" + babel-jest: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + regenerator-runtime: "npm:^0.13.2" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/6eddbd33e3e057a04e0ddd44ad0a40f36f122095aa9709004d5e608da97314452ca023df63c69592fa0efe58a98de2eb166c015fd130d39140476ed5d3c321d6 + languageName: node + linkType: hard + +"@react-native/js-polyfills@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/js-polyfills@npm:0.86.3" + checksum: 10c0/8b1fca9f5405cb556d28afb09fc6aa6f3117c1e550269ece8b4f8c61e9f13d278fc92d8d2e055663d5cdda9e7896be8761b3beb420c25fdaffec00e3540cd7b8 + languageName: node + linkType: hard + +"@react-native/metro-babel-transformer@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/metro-babel-transformer@npm:0.86.3" + dependencies: + "@babel/core": "npm:^7.25.2" + "@react-native/babel-preset": "npm:0.86.3" + hermes-parser: "npm:0.36.0" + nullthrows: "npm:^1.1.1" + peerDependencies: + "@babel/core": "*" + checksum: 10c0/40780e478c6be4bf8a5e62fc6f1fffb7aa75a53ac98fd7239a80cfa0fb0542b4183fd850d6e16325574c07e10e6b0934aa1b834876e1986d5cdef373def3ff8f languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/js-polyfills@npm:0.81.4" - checksum: 10c0/3a9fe20f257562c9971d1115973441fe41f8d4a8f3530aa5d269ce7dd0d25a4854f009b772e6f8d17b6ad5e88b739b06d3d6018228b4305c2922d3f087ef9697 +"@react-native/metro-config@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/metro-config@npm:0.86.3" + dependencies: + "@react-native/js-polyfills": "npm:0.86.3" + "@react-native/metro-babel-transformer": "npm:0.86.3" + metro-config: "npm:^0.84.3" + metro-runtime: "npm:^0.84.3" + checksum: 10c0/4bafb0a7b5b3de7b85f700530b462a1739c7437e96a97c40a90a03f44a094286fa8447f846c24c89aebb3e43992dc87ff9a72b7c2226faa3a6820e26c0a9cbb6 languageName: node linkType: hard -"@react-native/normalize-colors@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/normalize-colors@npm:0.81.4" - checksum: 10c0/d08de08ccbc47e2e8b8c9a258f2d38de02f9970067ce570961326ec916216ec2bcb48f28682b5b77d6a02f8d1734f02f181455915b6a0c1f145edca683545a9a +"@react-native/normalize-colors@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/normalize-colors@npm:0.86.3" + checksum: 10c0/33893681bdb3235d35dcb9f2fff5e1b2b0996be08c3cef24afed32c3a931298349ad5cbd33c3bb7fbf506f735d3e37995684d26ba9241795bbd3e709bd8d3165 languageName: node linkType: hard @@ -4384,20 +4414,20 @@ __metadata: languageName: node linkType: hard -"@react-native/virtualized-lists@npm:0.81.4": - version: 0.81.4 - resolution: "@react-native/virtualized-lists@npm:0.81.4" +"@react-native/virtualized-lists@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/virtualized-lists@npm:0.86.3" dependencies: invariant: "npm:^2.2.4" nullthrows: "npm:^1.1.1" peerDependencies: - "@types/react": ^19.1.0 + "@types/react": ^19.2.0 react: "*" - react-native: "*" + react-native: 0.86.3 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/dfd7ed38b844a47869d4c0238ad639f3770a8cfc7ac7a3c682f17703c38370937182c0f05b8ac91ed79e74475c4e10c1ddbdcca4050eaf38150b39d579799cca + checksum: 10c0/eee86f802cda4e95dc414ff27f7283a505e19d58d2f00d1a577c0d6b33b3d25439d239ea3c24033dd5820cebf970162f458c710695991bfe5794b9f8f1129159 languageName: node linkType: hard @@ -4847,7 +4877,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^19.1.10": +"@types/react@npm:*": version: 19.1.13 resolution: "@types/react@npm:19.1.13" dependencies: @@ -4856,6 +4886,15 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:~19.2.0": + version: 19.2.18 + resolution: "@types/react@npm:19.2.18" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/d04216172b4b4362b310017c210dfbe019fb4f4e7dffd0313e70b3acb3051d5c3e76e7e84e3cf4c6a3c824993ffadfe3949e589a6398a09d4200e7670e2962de + languageName: node + linkType: hard + "@types/semver@npm:^7.5.5": version: 7.7.1 resolution: "@types/semver@npm:7.7.1" @@ -5037,28 +5076,6 @@ __metadata: languageName: node linkType: hard -"@urql/core@npm:^5.0.6, @urql/core@npm:^5.1.2": - version: 5.2.0 - resolution: "@urql/core@npm:5.2.0" - dependencies: - "@0no-co/graphql.web": "npm:^1.0.13" - wonka: "npm:^6.3.2" - checksum: 10c0/1893a7417c6e5e3604fc3bd27e8b63b748d1817fb906fa95beba52be103d18e015d20ca740a9be570bca2bd11e98d34f605e108a4d3428678d9a1e3368ab2275 - languageName: node - linkType: hard - -"@urql/exchange-retry@npm:^1.3.0": - version: 1.3.2 - resolution: "@urql/exchange-retry@npm:1.3.2" - dependencies: - "@urql/core": "npm:^5.1.2" - wonka: "npm:^6.3.2" - peerDependencies: - "@urql/core": ^5.0.0 - checksum: 10c0/3d7e9879aef81714ca04c8b4dc1633d61b2090f5a6d5b8aac538633d41c26e8749e18bf66bed2936b209d038b3acdd21f20bc5ec4229a449b5a0b13d5694effe - languageName: node - linkType: hard - "@xmldom/xmldom@npm:^0.8.8": version: 0.8.13 resolution: "@xmldom/xmldom@npm:0.8.13" @@ -5101,7 +5118,7 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.7, accepts@npm:^1.3.8": +"accepts@npm:^1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -5111,6 +5128,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef + languageName: node + linkType: hard + "acorn-globals@npm:^7.0.0": version: 7.0.1 resolution: "acorn-globals@npm:7.0.1" @@ -5178,6 +5205,15 @@ __metadata: languageName: node linkType: hard +"agent-cli-detector@npm:0.1.7": + version: 0.1.7 + resolution: "agent-cli-detector@npm:0.1.7" + bin: + agent-cli-detector: dist/cli.js + checksum: 10c0/4ff016e5fad2d96a0c53db9fd75c2359bb005a672969e8e6e7bce5c910e94107c226cbcba1303409d1da3a1cdf8f22290e63049705bb530823cce4699d39fc1d + languageName: node + linkType: hard + "ajv@npm:^6.12.4": version: 6.12.6 resolution: "ajv@npm:6.12.6" @@ -5368,13 +5404,6 @@ __metadata: languageName: node linkType: hard -"async-limiter@npm:~1.0.0": - version: 1.0.1 - resolution: "async-limiter@npm:1.0.1" - checksum: 10c0/0693d378cfe86842a70d4c849595a0bb50dc44c11649640ca982fa90cbfc74e3cc4753b5a0847e51933f2e9c65ce8e05576e75e5e1fd963a086e673735b35969 - languageName: node - linkType: hard - "async-retry@npm:1.3.3": version: 1.3.3 resolution: "async-retry@npm:1.3.3" @@ -5505,6 +5534,15 @@ __metadata: languageName: node linkType: hard +"babel-plugin-react-compiler@npm:^1.0.0": + version: 1.0.0 + resolution: "babel-plugin-react-compiler@npm:1.0.0" + dependencies: + "@babel/types": "npm:^7.26.0" + checksum: 10c0/9406267ada8d7dbdfe8906b40ecadb816a5f4cee2922bee23f7729293b369624ee135b5a9b0f263851c263c9787522ac5d97016c9a2b82d1668300e42b18aff8 + languageName: node + linkType: hard + "babel-plugin-react-compiler@npm:^19.1.0-rc.2": version: 19.1.0-rc.3 resolution: "babel-plugin-react-compiler@npm:19.1.0-rc.3" @@ -5521,12 +5559,12 @@ __metadata: languageName: node linkType: hard -"babel-plugin-syntax-hermes-parser@npm:0.29.1, babel-plugin-syntax-hermes-parser@npm:^0.29.1": - version: 0.29.1 - resolution: "babel-plugin-syntax-hermes-parser@npm:0.29.1" +"babel-plugin-syntax-hermes-parser@npm:0.36.0": + version: 0.36.0 + resolution: "babel-plugin-syntax-hermes-parser@npm:0.36.0" dependencies: - hermes-parser: "npm:0.29.1" - checksum: 10c0/a6d95e4a7079976e477636d18509272a7a185930e143c61d0421a36096e85905563630ac4f0f317518b6db37f50daaefc1828d575b3d5fb090a55e9d39d2534c + hermes-parser: "npm:0.36.0" + checksum: 10c0/3c20d2b2595e890bf7bf14b48a1dc6eb2ad2625e04f942254f3676440e20489eb8266fba8f8077194d294dfb67552b6b969bb40b93c873b88fdc319528dc2606 languageName: node linkType: hard @@ -5539,6 +5577,15 @@ __metadata: languageName: node linkType: hard +"babel-plugin-syntax-hermes-parser@npm:^0.36.0": + version: 0.36.1 + resolution: "babel-plugin-syntax-hermes-parser@npm:0.36.1" + dependencies: + hermes-parser: "npm:0.36.1" + checksum: 10c0/d6343ef16032253408cc8f4585c7acc275101f3194c312606e0f0d65ccf768db36b74e907296a93f3844c108ed19776052d5e844cbd21ec18c43a23ce7c2055e + languageName: node + linkType: hard + "babel-plugin-tester@npm:^12.0.0": version: 12.0.0 resolution: "babel-plugin-tester@npm:12.0.0" @@ -5591,42 +5638,65 @@ __metadata: languageName: node linkType: hard -"babel-preset-expo@npm:~54.0.3": - version: 54.0.3 - resolution: "babel-preset-expo@npm:54.0.3" +"babel-preset-expo@npm:57.0.11, babel-preset-expo@npm:~57.0.11": + version: 57.0.11 + resolution: "babel-preset-expo@npm:57.0.11" dependencies: + "@babel/generator": "npm:^7.20.5" "@babel/helper-module-imports": "npm:^7.25.9" "@babel/plugin-proposal-decorators": "npm:^7.12.9" "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" + "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" + "@babel/plugin-transform-block-scoping": "npm:^7.25.0" + "@babel/plugin-transform-class-properties": "npm:^7.25.4" "@babel/plugin-transform-class-static-block": "npm:^7.27.1" + "@babel/plugin-transform-classes": "npm:^7.25.4" + "@babel/plugin-transform-destructuring": "npm:^7.24.8" "@babel/plugin-transform-export-namespace-from": "npm:^7.25.9" "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" + "@babel/plugin-transform-for-of": "npm:^7.24.7" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" "@babel/plugin-transform-parameters": "npm:^7.24.7" "@babel/plugin-transform-private-methods": "npm:^7.24.7" "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" + "@babel/plugin-transform-react-display-name": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx": "npm:^7.28.6" + "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" + "@babel/plugin-transform-react-pure-annotations": "npm:^7.27.1" "@babel/plugin-transform-runtime": "npm:^7.24.7" - "@babel/preset-react": "npm:^7.22.15" + "@babel/plugin-transform-typescript": "npm:^7.25.2" + "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" "@babel/preset-typescript": "npm:^7.23.0" - "@react-native/babel-preset": "npm:0.81.4" - babel-plugin-react-compiler: "npm:^19.1.0-rc.2" + "@react-native/babel-plugin-codegen": "npm:0.86.3" + babel-plugin-react-compiler: "npm:^1.0.0" babel-plugin-react-native-web: "npm:~0.21.0" - babel-plugin-syntax-hermes-parser: "npm:^0.29.1" + babel-plugin-syntax-hermes-parser: "npm:^0.36.0" babel-plugin-transform-flow-enums: "npm:^0.0.2" debug: "npm:^4.3.4" - resolve-from: "npm:^5.0.0" peerDependencies: "@babel/runtime": ^7.20.0 expo: "*" + expo-widgets: ^57.0.18 react-refresh: ">=0.14.0 <1.0.0" peerDependenciesMeta: "@babel/runtime": optional: true expo: optional: true - checksum: 10c0/d834cfdeaa70cd7d3784d328ccb5b5d29f4c807e49412480e876f1e92eedc64f6634832d44e02df9d63cc4bec5b408d6b9df97d836abd2ce971a14be52dba715 + expo-widgets: + optional: true + checksum: 10c0/244cd46902588ba688df5efee77179ec788ba121acf65a19b0b3d795b76c42b46104421920883be17731a0fec59335371ace472cbed0c33a85e0dca06111fdbc languageName: node linkType: hard @@ -5656,7 +5726,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": +"base64-js@npm:^1.2.3, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf @@ -5695,15 +5765,6 @@ __metadata: languageName: node linkType: hard -"better-opn@npm:~3.0.2": - version: 3.0.2 - resolution: "better-opn@npm:3.0.2" - dependencies: - open: "npm:^8.0.4" - checksum: 10c0/911ef25d44da75aabfd2444ce7a4294a8000ebcac73068c04a60298b0f7c7506b60421aa4cd02ac82502fb42baaff7e4892234b51e6923eded44c5a11185f2f5 - languageName: node - linkType: hard - "big-integer@npm:1.6.x": version: 1.6.52 resolution: "big-integer@npm:1.6.52" @@ -5821,16 +5882,6 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.4.3": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e - languageName: node - linkType: hard - "bundle-name@npm:^4.1.0": version: 4.1.0 resolution: "bundle-name@npm:4.1.0" @@ -5902,31 +5953,6 @@ __metadata: languageName: node linkType: hard -"caller-callsite@npm:^2.0.0": - version: 2.0.0 - resolution: "caller-callsite@npm:2.0.0" - dependencies: - callsites: "npm:^2.0.0" - checksum: 10c0/a00ca91280e10ee2321de21dda6c168e427df7a63aeaca027ea45e3e466ac5e1a5054199f6547ba1d5a513d3b6b5933457266daaa47f8857fb532a343ee6b5e1 - languageName: node - linkType: hard - -"caller-path@npm:^2.0.0": - version: 2.0.0 - resolution: "caller-path@npm:2.0.0" - dependencies: - caller-callsite: "npm:^2.0.0" - checksum: 10c0/029b5b2c557d831216305c3218e9ff30fa668be31d58dd08088f74c8eabc8362c303e0908b3a93abb25ba10e3a5bfc9cff5eb7fab6ab9cf820e3b160ccb67581 - languageName: node - linkType: hard - -"callsites@npm:^2.0.0": - version: 2.0.0 - resolution: "callsites@npm:2.0.0" - checksum: 10c0/13bff4fee946e6020b37e76284e95e24aa239c9e34ac4f3451e4c5330fca6f2f962e1d1ab69e4da7940e1fce135107a2b2b98c01d62ea33144350fc89dc5494e - languageName: node - linkType: hard - "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -6051,17 +6077,16 @@ __metadata: languageName: node linkType: hard -"chromium-edge-launcher@npm:^0.2.0": - version: 0.2.0 - resolution: "chromium-edge-launcher@npm:0.2.0" +"chromium-edge-launcher@npm:^0.3.0": + version: 0.3.0 + resolution: "chromium-edge-launcher@npm:0.3.0" dependencies: "@types/node": "npm:*" escape-string-regexp: "npm:^4.0.0" is-wsl: "npm:^2.2.0" lighthouse-logger: "npm:^1.0.0" mkdirp: "npm:^1.0.4" - rimraf: "npm:^3.0.2" - checksum: 10c0/880972816dd9b95c0eb77d1f707569667a8cce7cc29fe9c8d199c47fdfbe4971e9da3e5a29f61c4ecec29437ac7cebbbb5afc30bec96306579d1121e7340606a + checksum: 10c0/ad04a75bf53ebed0b7adc5bd133587369b0c2e55c92fe460eb6ccec5efe03c161a7466756173969867a2acbe02dd40449186bd74671dd892520492283d4ff43d languageName: node linkType: hard @@ -6634,18 +6659,6 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^5.0.5": - version: 5.2.1 - resolution: "cosmiconfig@npm:5.2.1" - dependencies: - import-fresh: "npm:^2.0.0" - is-directory: "npm:^0.3.1" - js-yaml: "npm:^3.13.1" - parse-json: "npm:^4.0.0" - checksum: 10c0/ae9ba309cdbb42d0c9d63dad5c1dfa1c56bb8f818cb8633eea14fd2dbdc9f33393b77658ba96fdabda497bc943afed8c3371d1222afe613c518ba676fa624645 - languageName: node - linkType: hard - "cosmiconfig@npm:^9.0.0": version: 9.0.0 resolution: "cosmiconfig@npm:9.0.0" @@ -6700,13 +6713,6 @@ __metadata: languageName: node linkType: hard -"crypto-random-string@npm:^2.0.0": - version: 2.0.0 - resolution: "crypto-random-string@npm:2.0.0" - checksum: 10c0/288589b2484fe787f9e146f56c4be90b940018f17af1b152e4dde12309042ff5a2bf69e949aab8b8ac253948381529cc6f3e5a2427b73643a71ff177fa122b37 - languageName: node - linkType: hard - "css-in-js-utils@npm:^3.1.0": version: 3.1.0 resolution: "css-in-js-utils@npm:3.1.0" @@ -6746,6 +6752,13 @@ __metadata: languageName: node linkType: hard +"csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + "dargs@npm:^8.0.0": version: 8.1.0 resolution: "dargs@npm:8.1.0" @@ -6832,13 +6845,6 @@ __metadata: languageName: node linkType: hard -"deep-extend@npm:^0.6.0": - version: 0.6.0 - resolution: "deep-extend@npm:0.6.0" - checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -6879,13 +6885,6 @@ __metadata: languageName: node linkType: hard -"define-lazy-prop@npm:^2.0.0": - version: 2.0.0 - resolution: "define-lazy-prop@npm:2.0.0" - checksum: 10c0/db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 - languageName: node - linkType: hard - "define-lazy-prop@npm:^3.0.0": version: 3.0.0 resolution: "define-lazy-prop@npm:3.0.0" @@ -6977,6 +6976,13 @@ __metadata: languageName: node linkType: hard +"dnssd-advertise@npm:^1.1.4": + version: 1.1.6 + resolution: "dnssd-advertise@npm:1.1.6" + checksum: 10c0/6f0639a45b2d6ae7b285501b711c314893c46eb2966539674220abb7b9c9c7b00de7bf0cfcd4cad0413f0e614ac7e2399e70faeb8bf7b875d9dc7be67862e4a7 + languageName: node + linkType: hard + "domexception@npm:^4.0.0": version: 4.0.0 resolution: "domexception@npm:4.0.0" @@ -6995,22 +7001,6 @@ __metadata: languageName: node linkType: hard -"dotenv-expand@npm:~11.0.6": - version: 11.0.7 - resolution: "dotenv-expand@npm:11.0.7" - dependencies: - dotenv: "npm:^16.4.5" - checksum: 10c0/d80b8a7be085edf351270b96ac0e794bc3ddd7f36157912939577cb4d33ba6492ebee349d59798b71b90e36f498d24a2a564fb4aa00073b2ef4c2a3a49c467b1 - languageName: node - linkType: hard - -"dotenv@npm:^16.4.5": - version: 16.6.1 - resolution: "dotenv@npm:16.6.1" - checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc - languageName: node - linkType: hard - "dotenv@npm:^17.2.3": version: 17.4.2 resolution: "dotenv@npm:17.4.2" @@ -7018,13 +7008,6 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:~16.4.5": - version: 16.4.7 - resolution: "dotenv@npm:16.4.7" - checksum: 10c0/be9f597e36a8daf834452daa1f4cc30e5375a5968f98f46d89b16b983c567398a330580c88395069a77473943c06b877d1ca25b4afafcdd6d4adb549e8293462 - languageName: node - linkType: hard - "dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -7151,13 +7134,6 @@ __metadata: languageName: node linkType: hard -"env-editor@npm:^0.4.1": - version: 0.4.2 - resolution: "env-editor@npm:0.4.2" - checksum: 10c0/edb33583b0ae5197535905cbcefca424796f6afec799604f7578428ee523245edcd7df48d582fdab67dbcc697ed39070057f512e72f94c91ceefdcb432f5eadb - languageName: node - linkType: hard - "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -7464,13 +7440,6 @@ __metadata: languageName: node linkType: hard -"exec-async@npm:^2.2.0": - version: 2.2.0 - resolution: "exec-async@npm:2.2.0" - checksum: 10c0/9c70693a3d9f53e19cc8ecf26c3b3fc7125bf40051a71cba70d71161d065a6091d3ab1924c56ac1edd68cb98b9fbef29f83e45dcf67ee6b6c4826e0f898ac039 - languageName: node - linkType: hard - "execa@npm:^5.0.0": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -7529,149 +7498,175 @@ __metadata: languageName: node linkType: hard -"expo-asset@npm:~12.0.9": - version: 12.0.9 - resolution: "expo-asset@npm:12.0.9" +"expo-asset@npm:~57.0.17": + version: 57.0.17 + resolution: "expo-asset@npm:57.0.17" dependencies: - "@expo/image-utils": "npm:^0.8.7" - expo-constants: "npm:~18.0.9" + "@expo/image-utils": "npm:^0.11.5" + expo-constants: "npm:~57.0.18" peerDependencies: expo: "*" react: "*" react-native: "*" - checksum: 10c0/7a66523e26e9868ad7961c9d6436f188e2832a7b3168a47be10ae8468616250803d31ded11b61bcc920e2aa0f0443c69fcb220be16b0b120aa255a840a355032 + checksum: 10c0/790623f1ac8a8ddab46d835c04bed43223e2235acd6a42f111f91527b723b3f9905afdc7c0e77ac71b14094ae81fee3958dd6a9199282b086c7601b8a3967c86 languageName: node linkType: hard -"expo-constants@npm:~18.0.9": - version: 18.0.9 - resolution: "expo-constants@npm:18.0.9" +"expo-constants@npm:~57.0.18": + version: 57.0.18 + resolution: "expo-constants@npm:57.0.18" dependencies: - "@expo/config": "npm:~12.0.9" - "@expo/env": "npm:~2.0.7" + "@expo/env": "npm:~2.4.3" peerDependencies: expo: "*" react-native: "*" - checksum: 10c0/5e7f2fd366d4c0351d2d30b8f31afc0001e1d0c19f113b9083bae7ee495cb55aeae402497d0a13f856157730d5821c5143acccfc3d8d25b74aa064c6d33ec017 + checksum: 10c0/80020a95c36021a5ef084e0a5f9611d63b8375c86b767ae475ec074ab8f5f71a0dc0014fffaff3f7c7e7995861e316f6a2ce5580cbfc89767077ca4814ebc92f languageName: node linkType: hard -"expo-file-system@npm:~19.0.15": - version: 19.0.15 - resolution: "expo-file-system@npm:19.0.15" +"expo-file-system@npm:~57.0.7": + version: 57.0.7 + resolution: "expo-file-system@npm:57.0.7" peerDependencies: expo: "*" react-native: "*" - checksum: 10c0/cd283b1bfc79eba058f47661fc2455c54911f56862bd705ff1ed5fdf6020b40df8fa7feb66bad28247e783411c7e6851e8e6260b36be139005ca1704db9d84cb + checksum: 10c0/ca02903e7b918f2b73f9f009d13e2f3f2eda46b04d479af770385c463631feb843f6a742c34a75ad7e892862dfb4b4311fa0520d7599a9f01e478014b105d3b0 languageName: node linkType: hard -"expo-font@npm:~14.0.8": - version: 14.0.8 - resolution: "expo-font@npm:14.0.8" +"expo-font@npm:~57.0.4": + version: 57.0.4 + resolution: "expo-font@npm:57.0.4" dependencies: fontfaceobserver: "npm:^2.1.0" peerDependencies: expo: "*" react: "*" react-native: "*" - checksum: 10c0/1238c23436431f283687bc465e9ce99702db3fc99e00bd3ebe0391090d5378a4c186fd6f9c124f3e00874dc5259c6304cf16bc0e8842674e590d7d46016dd5dc + checksum: 10c0/b9e13197971c3a3b354e498315033e11d4a834de695637914273225a24fd7c88172b23ab7eb1dde88620dbe093b6958d361751d839b87d4bf09d9ad5ca2b0117 languageName: node linkType: hard -"expo-keep-awake@npm:~15.0.7": - version: 15.0.7 - resolution: "expo-keep-awake@npm:15.0.7" +"expo-keep-awake@npm:~57.0.2": + version: 57.0.2 + resolution: "expo-keep-awake@npm:57.0.2" peerDependencies: expo: "*" react: "*" - checksum: 10c0/6ca4cb430a97627b5657a220720808e4bd6dd89f4e9f86d52db71b9f91a72af8e63a83005d49af86924fc8f7bd210c312dfcb07212a1fe54334e0b4058943ec9 + checksum: 10c0/2e7cd81adea1c62f49f4e9ba76b606960969779222626abc90c6b4cd872c0e47c062eebb5e1688550676f55300677348252d9fe8a7c95d03e1320496a2c136ca languageName: node linkType: hard -"expo-modules-autolinking@npm:3.0.13": - version: 3.0.13 - resolution: "expo-modules-autolinking@npm:3.0.13" +"expo-modules-autolinking@npm:~57.0.13": + version: 57.0.13 + resolution: "expo-modules-autolinking@npm:57.0.13" dependencies: - "@expo/spawn-async": "npm:^1.7.2" + "@expo/require-utils": "npm:^57.0.5" + "@expo/spawn-async": "npm:^1.8.0" chalk: "npm:^4.1.0" commander: "npm:^7.2.0" - glob: "npm:^10.4.2" - require-from-string: "npm:^2.0.2" - resolve-from: "npm:^5.0.0" bin: expo-modules-autolinking: bin/expo-modules-autolinking.js - checksum: 10c0/4441e1de79c02fc4702bdd21d1e3a687183dea6f2fe0ae637b63ef4b63da38f47e2cdf92d213f7d2faa7179e3f3ec80bca7a648c535099b46aa97b946ac87b1b + checksum: 10c0/cf6105f72c4f5545d8d8791aeda3210ad0281919bf67ea2efa8956a6191a377e1d87885b11969bd18c79d1200ed695b1e27f96743c564ebb3a2d0a0ac5de7bcb languageName: node linkType: hard -"expo-modules-core@npm:3.0.18": - version: 3.0.18 - resolution: "expo-modules-core@npm:3.0.18" +"expo-modules-core@npm:~57.0.18": + version: 57.0.18 + resolution: "expo-modules-core@npm:57.0.18" dependencies: + "@expo/expo-modules-macros-plugin": "npm:0.6.1" + expo-modules-jsi: "npm:~57.1.0" invariant: "npm:^2.2.4" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/a65a7901632888ae79ad6fc9ab0cccee615a7cb87c5d219a1a7a4a3377bb66bad10bd81e8c313c96e8b8fcfb8b2a4fc0bf19d63d36326f46cd4119b5a3ac50cc + react-native-worklets: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + peerDependenciesMeta: + react-native-worklets: + optional: true + checksum: 10c0/7f3c02b5497b4d6bdcba66fc099e2de2ab242b8696c9d778d08b0b5b99002a721f8ae021329784f1aaefe5f10c018cf619a667bfa37370e378949e43c4ccfb6a languageName: node linkType: hard -"expo-status-bar@npm:~3.0.8": - version: 3.0.8 - resolution: "expo-status-bar@npm:3.0.8" - dependencies: - react-native-is-edge-to-edge: "npm:^1.2.1" +"expo-modules-jsi@npm:~57.1.0": + version: 57.1.0 + resolution: "expo-modules-jsi@npm:57.1.0" peerDependencies: + react-native: "*" + checksum: 10c0/41400e830b346e70db621abd66d1552932083f75af184cf043a2d322a55e5a9da9a34727be772180005da0eb883e191efedadf0dcf0de6bda715ae42814f524a + languageName: node + linkType: hard + +"expo-server@npm:^57.0.3": + version: 57.0.3 + resolution: "expo-server@npm:57.0.3" + checksum: 10c0/541d9b319ba513df718868c87a8d57e30161c3879b7d5b43f308923290ce5ee349a1b3469901c2f382984b6595f4cf6beaee9d870f5eb874c45ddc0a42a13e56 + languageName: node + linkType: hard + +"expo-status-bar@npm:~57.0.1": + version: 57.0.1 + resolution: "expo-status-bar@npm:57.0.1" + peerDependencies: + expo: "*" react: "*" react-native: "*" - checksum: 10c0/e077c164495783b5994718127e790d88bed31028d9562618fc697aeb4f9b22b34ca648d8cebbf55401f7bfce9c3b057a785175e64b533f35e33d45ff5f4b4600 + checksum: 10c0/c909e6b4d93591f47fd32f33c893df69639a5bc7538777c13407d34f9e0db3a3095b8710c284d5cefde6df482e49e402bcb3a489fd3bfe864de2cde891d71c97 languageName: node linkType: hard -"expo@npm:54.0.10": - version: 54.0.10 - resolution: "expo@npm:54.0.10" +"expo@npm:57.0.22": + version: 57.0.22 + resolution: "expo@npm:57.0.22" dependencies: "@babel/runtime": "npm:^7.20.0" - "@expo/cli": "npm:54.0.8" - "@expo/config": "npm:~12.0.9" - "@expo/config-plugins": "npm:~54.0.1" - "@expo/devtools": "npm:0.1.7" - "@expo/fingerprint": "npm:0.15.1" - "@expo/metro": "npm:~54.0.0" - "@expo/metro-config": "npm:54.0.5" - "@expo/vector-icons": "npm:^15.0.2" + "@expo/cli": "npm:^57.0.24" + "@expo/config": "npm:~57.0.9" + "@expo/config-plugins": "npm:~57.0.9" + "@expo/devtools": "npm:~57.0.1" + "@expo/dom-webview": "npm:~57.0.1" + "@expo/fingerprint": "npm:^0.20.13" + "@expo/local-build-cache-provider": "npm:^57.0.8" + "@expo/log-box": "npm:^57.0.4" + "@expo/metro": "npm:~56.0.2" + "@expo/metro-config": "npm:~57.0.12" "@ungap/structured-clone": "npm:^1.3.0" - babel-preset-expo: "npm:~54.0.3" - expo-asset: "npm:~12.0.9" - expo-constants: "npm:~18.0.9" - expo-file-system: "npm:~19.0.15" - expo-font: "npm:~14.0.8" - expo-keep-awake: "npm:~15.0.7" - expo-modules-autolinking: "npm:3.0.13" - expo-modules-core: "npm:3.0.18" + babel-preset-expo: "npm:~57.0.11" + expo-asset: "npm:~57.0.17" + expo-constants: "npm:~57.0.18" + expo-file-system: "npm:~57.0.7" + expo-font: "npm:~57.0.4" + expo-keep-awake: "npm:~57.0.2" + expo-modules-autolinking: "npm:~57.0.13" + expo-modules-core: "npm:~57.0.18" pretty-format: "npm:^29.7.0" react-refresh: "npm:^0.14.2" - whatwg-url-without-unicode: "npm:8.0.0-3" + whatwg-url-minimum: "npm:^0.1.2" peerDependencies: "@expo/dom-webview": "*" "@expo/metro-runtime": "*" react: "*" + react-dom: "*" react-native: "*" + react-native-web: "*" react-native-webview: "*" peerDependenciesMeta: "@expo/dom-webview": optional: true "@expo/metro-runtime": optional: true + react-dom: + optional: true + react-native-web: + optional: true react-native-webview: optional: true bin: expo: bin/cli expo-modules-autolinking: bin/autolinking fingerprint: bin/fingerprint - checksum: 10c0/81f7b655686e407ba224cc2fba088756b5487ffec0b43134c7526be2fbb53276cb84b220f106b45773a9202edc8147d35e09df80e261aafbe47c05bc6e256046 + checksum: 10c0/c41a130c0346f524fcdde85320d0b4d34698e79612764880444dc294fce0d2727139018f70c217419f6123cf6c4867dbae75c97509184da5f39149201b54bd5e languageName: node linkType: hard @@ -7778,7 +7773,16 @@ __metadata: languageName: node linkType: hard -"fb-watchman@npm:^2.0.0": +"fb-dotslash@npm:0.5.8": + version: 0.5.8 + resolution: "fb-dotslash@npm:0.5.8" + bin: + dotslash: bin/dotslash + checksum: 10c0/6c693ecb8e61cd8571e0ad6a923e0582cf8e481695e906e17c8e31620402e06f8b80d95111a420d2f62349d9bebc2b820bae14c2c54a814e72abdc710dc1d3ed + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0, fb-watchman@npm:^2.0.2": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" dependencies: @@ -7821,6 +7825,13 @@ __metadata: languageName: node linkType: hard +"fetch-nodeshim@npm:^0.4.10": + version: 0.4.10 + resolution: "fetch-nodeshim@npm:0.4.10" + checksum: 10c0/73b840b5d1252e82c416b350526ff24f5aebf554bfe911c713a19fbe4ad1218fb4c488f95055362a132f5dd733679c929fbe6a65ee23339592290c4d107ade92 + languageName: node + linkType: hard + "figures@npm:3.2.0": version: 3.2.0 resolution: "figures@npm:3.2.0" @@ -7955,13 +7966,6 @@ __metadata: languageName: node linkType: hard -"freeport-async@npm:^2.0.0": - version: 2.0.0 - resolution: "freeport-async@npm:2.0.0" - checksum: 10c0/421828d1a689695b6c8122d310fd8941af99ebe0b5793e3f8d49aa5923ce580b6c4dd6b7470d46983e60839c302f6c793a8541dbab80817396cdde2b04c83c90 - languageName: node - linkType: hard - "fresh@npm:0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" @@ -8234,7 +8238,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^13.0.6": +"glob@npm:^13.0.0, glob@npm:^13.0.6": version: 13.0.6 resolution: "glob@npm:13.0.6" dependencies: @@ -8245,7 +8249,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4": +"glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -8260,20 +8264,11 @@ __metadata: linkType: hard "global-directory@npm:^4.0.1": - version: 4.0.1 - resolution: "global-directory@npm:4.0.1" - dependencies: - ini: "npm:4.1.1" - checksum: 10c0/f9cbeef41db4876f94dd0bac1c1b4282a7de9c16350ecaaf83e7b2dd777b32704cc25beeb1170b5a63c42a2c9abfade74d46357fe0133e933218bc89e613d4b2 - languageName: node - linkType: hard - -"global-dirs@npm:^0.1.1": - version: 0.1.1 - resolution: "global-dirs@npm:0.1.1" + version: 4.0.1 + resolution: "global-directory@npm:4.0.1" dependencies: - ini: "npm:^1.3.4" - checksum: 10c0/3608072e58962396c124ad5a1cfb3f99ee76c998654a3432d82977b3c3eeb09dc8a5a2a9849b2b8113906c8d0aad89ce362c22e97cec5fe34405bbf4f3cdbe7a + ini: "npm:4.1.1" + checksum: 10c0/f9cbeef41db4876f94dd0bac1c1b4282a7de9c16350ecaaf83e7b2dd777b32704cc25beeb1170b5a63c42a2c9abfade74d46357fe0133e933218bc89e613d4b2 languageName: node linkType: hard @@ -8385,17 +8380,10 @@ __metadata: languageName: node linkType: hard -"hermes-estree@npm:0.29.1": - version: 0.29.1 - resolution: "hermes-estree@npm:0.29.1" - checksum: 10c0/e6b01f79ba708697d61a74b871d5ebae5f863c6d782657d8e2d2256eb838f1eb86ff9c34773a81d9cc69e54be3a5059c686e0ab54a4afba903b40dde92dd0ccb - languageName: node - linkType: hard - -"hermes-estree@npm:0.32.0": - version: 0.32.0 - resolution: "hermes-estree@npm:0.32.0" - checksum: 10c0/3b67d1fe44336240ef7f9c40ecbf363279ba263d51efe120570c3862cc109e652fc09aebddfe6b73d0f0246610bee130e4064c359f1f4cbf002bdb1d99717ef2 +"hermes-compiler@npm:250829098.0.17": + version: 250829098.0.17 + resolution: "hermes-compiler@npm:250829098.0.17" + checksum: 10c0/1bcfcb9268b18593e19e7d4e071dd644cbcff380535a74ea0318b417e30ba190b33e70086f8576c3b28deb2572ba01f57b1a9e5ae10f6841dedaff281b037200 languageName: node linkType: hard @@ -8406,21 +8394,24 @@ __metadata: languageName: node linkType: hard -"hermes-parser@npm:0.29.1, hermes-parser@npm:^0.29.1": - version: 0.29.1 - resolution: "hermes-parser@npm:0.29.1" - dependencies: - hermes-estree: "npm:0.29.1" - checksum: 10c0/7f40d9bdfb5acaa700f333a24c644b17f5f8d0e823b1e7a9fb6dcf253a54d54716ae63c74effa023688ee4f09013c80188c40d601570fee256a44954e04c2926 +"hermes-estree@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-estree@npm:0.35.0" + checksum: 10c0/a88c9dc63b8b3679b1aeb43e72e977597096c1bd7d59978c952f1d6df6d1a517c4a817c70b1b701854996b485adfa66c2fc7f80871029a7f0c04306f6717b59a languageName: node linkType: hard -"hermes-parser@npm:0.32.0": - version: 0.32.0 - resolution: "hermes-parser@npm:0.32.0" - dependencies: - hermes-estree: "npm:0.32.0" - checksum: 10c0/5902d2c5d347c0629fba07a47eaad5569590ac69bc8bfb2e454e08d2dfbe1ebd989d88518dca2cba64061689b5eac5960ae6bd15a4a66600bbf377498a3234b7 +"hermes-estree@npm:0.36.0": + version: 0.36.0 + resolution: "hermes-estree@npm:0.36.0" + checksum: 10c0/fe27f57b0f8c3921e9dc48c517e25f4399609ca386353f90e04d3f859bacbee93184af80a6b930db5e981e8953a5c4083147556b21b19519917e131e7533b27c + languageName: node + linkType: hard + +"hermes-estree@npm:0.36.1": + version: 0.36.1 + resolution: "hermes-estree@npm:0.36.1" + checksum: 10c0/9f552e1f809e05a4ddc5dcc10c5d1d5e375eade985026c3f8876a227db76c33059d25f93f92628a0e43d29a6da6977fbb643fafc8830babad62c1b88b2b56739 languageName: node linkType: hard @@ -8433,6 +8424,33 @@ __metadata: languageName: node linkType: hard +"hermes-parser@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-parser@npm:0.35.0" + dependencies: + hermes-estree: "npm:0.35.0" + checksum: 10c0/49d98093a2094758db5b536627c6cf5146b140f66e63143acf471c62f1d3fd8bd6ae10a33f2372f72e3653deda5d4615c6dae89d01248849440916209901fc4a + languageName: node + linkType: hard + +"hermes-parser@npm:0.36.0": + version: 0.36.0 + resolution: "hermes-parser@npm:0.36.0" + dependencies: + hermes-estree: "npm:0.36.0" + checksum: 10c0/76e726366ac2ea91e9464853f439d582ddee9c07ccb84cac35297ce1a5d197bf96b84ab030423a22940b55a0fa5bb191bc0ebeebd95f8e34116900decdccdb86 + languageName: node + linkType: hard + +"hermes-parser@npm:0.36.1, hermes-parser@npm:^0.36.0": + version: 0.36.1 + resolution: "hermes-parser@npm:0.36.1" + dependencies: + hermes-estree: "npm:0.36.1" + checksum: 10c0/31f2b0a383e15a3cdd8f34bc11498a35504771b58f24db5694ea5013faf6dbe6b27a47f7825bae5494dd57afac4de54c05f750d4ad646ff6787bbba97dfbd8eb + languageName: node + linkType: hard + "hosted-git-info@npm:^7.0.0": version: 7.0.2 resolution: "hosted-git-info@npm:7.0.2" @@ -8571,13 +8589,6 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb - languageName: node - linkType: hard - "ignore@npm:^5.2.0, ignore@npm:^5.3.1": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -8592,27 +8603,6 @@ __metadata: languageName: node linkType: hard -"image-size@npm:^1.0.2": - version: 1.2.1 - resolution: "image-size@npm:1.2.1" - dependencies: - queue: "npm:6.0.2" - bin: - image-size: bin/image-size.js - checksum: 10c0/f8b3c19d4476513f1d7e55c3e6db80997b315444743e2040d545cbcaee59be03d2eb40c46be949a8372697b7003fdb0c04925d704390a7f606bc8181e25c0ed4 - languageName: node - linkType: hard - -"import-fresh@npm:^2.0.0": - version: 2.0.0 - resolution: "import-fresh@npm:2.0.0" - dependencies: - caller-path: "npm:^2.0.0" - resolve-from: "npm:^3.0.0" - checksum: 10c0/116c55ee5215a7839062285b60df85dbedde084c02111dc58c1b9d03ff7876627059f4beb16cdc090a3db21fea9022003402aa782139dc8d6302589038030504 - languageName: node - linkType: hard - "import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.1 resolution: "import-fresh@npm:3.3.1" @@ -8673,7 +8663,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -8687,13 +8677,6 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.4, ini@npm:~1.3.0": - version: 1.3.8 - resolution: "ini@npm:1.3.8" - checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a - languageName: node - linkType: hard - "inline-style-prefixer@npm:^7.0.1": version: 7.0.1 resolution: "inline-style-prefixer@npm:7.0.1" @@ -8744,14 +8727,7 @@ __metadata: languageName: node linkType: hard -"is-directory@npm:^0.3.1": - version: 0.3.1 - resolution: "is-directory@npm:0.3.1" - checksum: 10c0/1c39c7d1753b04e9483b89fb88908b8137ab4743b6f481947e97ccf93ecb384a814c8d3f0b95b082b149c5aa19c3e9e4464e2791d95174bce95998c26bb1974b - languageName: node - linkType: hard - -"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": +"is-docker@npm:^2.0.0": version: 2.2.1 resolution: "is-docker@npm:2.2.1" bin: @@ -9212,9 +9188,9 @@ __metadata: languageName: node linkType: hard -"jest-expo@npm:~56.0.5": - version: 56.0.5 - resolution: "jest-expo@npm:56.0.5" +"jest-expo@npm:57.0.5": + version: 57.0.5 + resolution: "jest-expo@npm:57.0.5" dependencies: "@jest/create-cache-key-function": "npm:^29.2.1" "@jest/globals": "npm:^29.2.1" @@ -9229,7 +9205,7 @@ __metadata: server-only: "npm:^0.0.1" stacktrace-js: "npm:^2.0.2" peerDependencies: - "@react-native/jest-preset": ^0.85.0 + "@react-native/jest-preset": ^0.86.3 expo: "*" react-native: "*" react-server-dom-webpack: ~19.0.4 || ~19.1.5 || ~19.2.4 @@ -9240,7 +9216,7 @@ __metadata: optional: true bin: jest: bin/jest.js - checksum: 10c0/c8cb78dba53d46ec89f3b94ce2d5f3343f9b96a209a4d3410369b62397a26a5c70bc3e59efcb4e89451ec0c4fb7cc5a86420d3d3c4f5c714facbbe5389ba3a31 + checksum: 10c0/0ca7cb6e3b5ae70c90548dada0a0a38e97855b8aa841440a057085deb601e8093354766558b7f94524749820da3c7becd8561f5a756bba3e37ee605be566490b languageName: node linkType: hard @@ -9747,13 +9723,6 @@ __metadata: languageName: node linkType: hard -"json-parse-better-errors@npm:^1.0.1": - version: 1.0.2 - resolution: "json-parse-better-errors@npm:1.0.2" - checksum: 10c0/2f1287a7c833e397c9ddd361a78638e828fc523038bb3441fd4fc144cfd2c6cd4963ffb9e207e648cf7b692600f1e1e524e965c32df5152120910e4903a47dcb - languageName: node - linkType: hard - "json-parse-even-better-errors@npm:^2.3.0": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" @@ -9841,12 +9810,12 @@ __metadata: languageName: node linkType: hard -"lan-network@npm:^0.1.6": - version: 0.1.7 - resolution: "lan-network@npm:0.1.7" +"lan-network@npm:^0.2.1": + version: 0.2.1 + resolution: "lan-network@npm:0.2.1" bin: lan-network: dist/lan-network-cli.js - checksum: 10c0/7afd3a7159bb65ff40bded481e4d522b1faa6b65e8b69d6404651d87fe800a35510aff9b913bb90def4f66ca886e28907492b8323f8c568830b42d28f521fb18 + checksum: 10c0/14995644bab174cde57e41c80ed828a52d6b788f8701b8a8347c536f3ecade3e71bd33b98091484b27a1df1e35b7f6f1921ac59521bf905b5dd06ab123e82e94 languageName: node linkType: hard @@ -10432,129 +10401,131 @@ __metadata: languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.1": - version: 0.83.1 - resolution: "metro-babel-transformer@npm:0.83.1" +"metro-babel-transformer@npm:0.84.5": + version: 0.84.5 + resolution: "metro-babel-transformer@npm:0.84.5" dependencies: "@babel/core": "npm:^7.25.2" flow-enums-runtime: "npm:^0.0.6" - hermes-parser: "npm:0.29.1" + hermes-parser: "npm:0.35.0" + metro-cache-key: "npm:0.84.5" nullthrows: "npm:^1.1.1" - checksum: 10c0/7e89744812a58fd6b9fa45141f0b34a9c23b895e2d92942415475493de668e4c17d1ec55c9d5be6b0d8c53651c64c2b73bb0ea2a08fdd1fb703b0c7c467de4a2 + checksum: 10c0/d105413dbe7887f05bf46b2c187e17abc7432bbabb6ac50caff2bf7460e5fa029654dd7478d502eb39987e0a1a0ec1d7660f1786cea53c7738da909ac319c59d languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.2": - version: 0.83.2 - resolution: "metro-babel-transformer@npm:0.83.2" +"metro-babel-transformer@npm:0.84.6": + version: 0.84.6 + resolution: "metro-babel-transformer@npm:0.84.6" dependencies: "@babel/core": "npm:^7.25.2" flow-enums-runtime: "npm:^0.0.6" - hermes-parser: "npm:0.32.0" + hermes-parser: "npm:0.35.0" + metro-cache-key: "npm:0.84.6" nullthrows: "npm:^1.1.1" - checksum: 10c0/8f3005c6534eb62816fa85a321c891b1dd64f4ac92d7dc7eedbfe06b4fffd2e3f629d0641ffd87373c6d28152c701faf36c7f7a4b0ed6624aa2b3c922d6026ae + checksum: 10c0/4bcdd9f695935384328e8be733328357d8e7a59ceb27d88643c419cc4c68dc09f156766817d031659e1e2509179f9869a9af173c84414f1db37bc301ebaaa17b languageName: node linkType: hard -"metro-cache-key@npm:0.83.1": - version: 0.83.1 - resolution: "metro-cache-key@npm:0.83.1" +"metro-cache-key@npm:0.84.5": + version: 0.84.5 + resolution: "metro-cache-key@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/16d3541a413a26723880512267f1986052fafa3d71da86edcdf24830236e657e29739153dcb5228e62e2817e2d73129a74eb65dde09d1e1763206a521e3d6b5c + checksum: 10c0/2e3507b31b2d21f833a5cf78fadbb3d7042921212c418ffc870aeef05759d954e20d347e9b2c1953847ca43c64e85cedfa7e0e2b7ad5dcb685826b2dc6346bb9 languageName: node linkType: hard -"metro-cache-key@npm:0.83.2": - version: 0.83.2 - resolution: "metro-cache-key@npm:0.83.2" +"metro-cache-key@npm:0.84.6": + version: 0.84.6 + resolution: "metro-cache-key@npm:0.84.6" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/791517bcd997d2f8ecaba3aff99714408f1c80d938846c7b8d114e346ce93c963eb103317e4781b3c16ad187ed95b8e23397346bb6cb4b86ec8ff95dbda4681e + checksum: 10c0/53be969296377c811e27730a150022c14fac2e4766993c030675868795ae5014b7381f576b1977155a9a7d5c1563f801e28d8f0d5403e313c0ab11882b23b976 languageName: node linkType: hard -"metro-cache@npm:0.83.1": - version: 0.83.1 - resolution: "metro-cache@npm:0.83.1" +"metro-cache@npm:0.84.5": + version: 0.84.5 + resolution: "metro-cache@npm:0.84.5" dependencies: exponential-backoff: "npm:^3.1.1" flow-enums-runtime: "npm:^0.0.6" https-proxy-agent: "npm:^7.0.5" - metro-core: "npm:0.83.1" - checksum: 10c0/ddeac25554aec4c19fc7c6fecff8c79af486cc41962ee3e58218a84dafd6d9b1664572f7179bc69a967a01696008bfb10300d4b5e31b6ae3220b69b3b0ef9f11 + metro-core: "npm:0.84.5" + checksum: 10c0/fa590e17d8e16274c09b874e34df5d6cbf8912aec329d563a1d751a4ebc8c8cfa7c6133df1a240be1e2ebdada1f1d65e30ea97dbf58549fbeec81d632c073be8 languageName: node linkType: hard -"metro-cache@npm:0.83.2": - version: 0.83.2 - resolution: "metro-cache@npm:0.83.2" +"metro-cache@npm:0.84.6": + version: 0.84.6 + resolution: "metro-cache@npm:0.84.6" dependencies: exponential-backoff: "npm:^3.1.1" flow-enums-runtime: "npm:^0.0.6" https-proxy-agent: "npm:^7.0.5" - metro-core: "npm:0.83.2" - checksum: 10c0/2c8d4004153431abb73265496ba2ede5e5fd09c2ca3769186ebe5b87645c97d74b871cc1bf83ab6bccdae596039341e330df3ebfab6e8469e1207e6a5d0e9ebc + metro-core: "npm:0.84.6" + checksum: 10c0/388b2535e8e7833dfbbcedd89a06c12e9ca22a1dd4fb1717f5c680112fbed1a6054869d2f599c5661c146327403c7056f453447da3b83bdd125f3525358970fc languageName: node linkType: hard -"metro-config@npm:0.83.1": - version: 0.83.1 - resolution: "metro-config@npm:0.83.1" +"metro-config@npm:0.84.5": + version: 0.84.5 + resolution: "metro-config@npm:0.84.5" dependencies: connect: "npm:^3.6.5" - cosmiconfig: "npm:^5.0.5" flow-enums-runtime: "npm:^0.0.6" jest-validate: "npm:^29.7.0" - metro: "npm:0.83.1" - metro-cache: "npm:0.83.1" - metro-core: "npm:0.83.1" - metro-runtime: "npm:0.83.1" - checksum: 10c0/8c5ffe2cb92bf96209b8ee0727c13980594601230a06ec78a56ffca5a9b1a4faa8f59a16db95a0bf2c9c56f35bcf918f22da25aa2192528f4700e7247733326d + metro: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-core: "npm:0.84.5" + metro-runtime: "npm:0.84.5" + yaml: "npm:^2.6.1" + checksum: 10c0/ba946982432e442a8187ca955a65dca18903c087e6f279433a9173a9283b72e32bb2bdd2ca0ccda491e5c068b9378725c6a6ba73dbe3e180cf0f089cd5523bdc languageName: node linkType: hard -"metro-config@npm:0.83.2, metro-config@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-config@npm:0.83.2" +"metro-config@npm:0.84.6, metro-config@npm:^0.84.3": + version: 0.84.6 + resolution: "metro-config@npm:0.84.6" dependencies: connect: "npm:^3.6.5" flow-enums-runtime: "npm:^0.0.6" jest-validate: "npm:^29.7.0" - metro: "npm:0.83.2" - metro-cache: "npm:0.83.2" - metro-core: "npm:0.83.2" - metro-runtime: "npm:0.83.2" + metro: "npm:0.84.6" + metro-cache: "npm:0.84.6" + metro-core: "npm:0.84.6" + metro-runtime: "npm:0.84.6" yaml: "npm:^2.6.1" - checksum: 10c0/224dff59b53f23ca4a0f39e8a82c297b93c779e9f92c1e097c1ac9fd66d86978b696476aef14a2eb3d2b4704adfb53e3d7ab76abf32924a06f3f6d0c586ac16f + checksum: 10c0/8c8dd3f738743fbdb5aeda75e3b500ced5d04032835c815cb0ff38e55e6319a6a86a82fbe1568b339947ad08cdae82199b6b6f941ce7860c6c2af642fd4d444a languageName: node linkType: hard -"metro-core@npm:0.83.1": - version: 0.83.1 - resolution: "metro-core@npm:0.83.1" +"metro-core@npm:0.84.5": + version: 0.84.5 + resolution: "metro-core@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" lodash.throttle: "npm:^4.1.1" - metro-resolver: "npm:0.83.1" - checksum: 10c0/c04b7fa05886d8e971e59446d380b21ee7031adc013740fe1ad41fef968235ab78eb5b592fc6e890dbc20192b4bbc83aa0ac93af717855a74b7629af46611291 + metro-resolver: "npm:0.84.5" + checksum: 10c0/f351c27f84f83bd3ec1db4e06566dfbfb01c6ebe193c4aa59850661374208b548e5738091525fe642a63c6117c64b053489f529e0aa29c311ff871cf290c00c3 languageName: node linkType: hard -"metro-core@npm:0.83.2, metro-core@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-core@npm:0.83.2" +"metro-core@npm:0.84.6, metro-core@npm:^0.84.3": + version: 0.84.6 + resolution: "metro-core@npm:0.84.6" dependencies: flow-enums-runtime: "npm:^0.0.6" lodash.throttle: "npm:^4.1.1" - metro-resolver: "npm:0.83.2" - checksum: 10c0/3582bfb114fbda2c9bb5cdde1a185ba0c707a27f325a6e63c6a981c9a05e02965dcfcc8c98cc37f30ef8e103f34e4f7b544e67f61f1dcdfc1bf81d98b27507f8 + metro-resolver: "npm:0.84.6" + checksum: 10c0/069b74c01471fcdfe13188ee087192e06ab2e42e769f6bbfe1068c6a7b4a1005d6adede0b1100e22b192a37e0397501c91b420f9669229d36253901d1fb59b53 languageName: node linkType: hard -"metro-file-map@npm:0.83.1": - version: 0.83.1 - resolution: "metro-file-map@npm:0.83.1" +"metro-file-map@npm:0.84.5": + version: 0.84.5 + resolution: "metro-file-map@npm:0.84.5" dependencies: debug: "npm:^4.4.0" fb-watchman: "npm:^2.0.0" @@ -10565,13 +10536,13 @@ __metadata: micromatch: "npm:^4.0.4" nullthrows: "npm:^1.1.1" walker: "npm:^1.0.7" - checksum: 10c0/970496de8befb9fdcdaa5742f9b895510a2a4ce463a7120d3335cdd49e97462b2d71b66a50477af046fe728ba9366b2a921ce4245b20d704a2ea85639d40ba9d + checksum: 10c0/16c0fbdc0e50c1785048e707f3b70813d89c72388ef024fcd16a78d18705e335c14649e842f594d5146d6f592616b4cc13953e6abbf0769006b5a6219d4c22d4 languageName: node linkType: hard -"metro-file-map@npm:0.83.2": - version: 0.83.2 - resolution: "metro-file-map@npm:0.83.2" +"metro-file-map@npm:0.84.6": + version: 0.84.6 + resolution: "metro-file-map@npm:0.84.6" dependencies: debug: "npm:^4.4.0" fb-watchman: "npm:^2.0.0" @@ -10582,65 +10553,65 @@ __metadata: micromatch: "npm:^4.0.4" nullthrows: "npm:^1.1.1" walker: "npm:^1.0.7" - checksum: 10c0/0577c06c7c7f1325a9c9121a304de13c632f783e4a4fe149b1b532ae88d375f199803ea75ff56e979867d4969326a297336ac815ee494ba057eec600a129bb58 + checksum: 10c0/7f9f3585b0e4caa80dc054e650c5aa0b1f42ee5fc58f9cb1d8895d29651fa3ea618e9c7bec4e1777a13ff6723812788b4ba241a7202eb6f571af97749c5c5bbe languageName: node linkType: hard -"metro-minify-terser@npm:0.83.1": - version: 0.83.1 - resolution: "metro-minify-terser@npm:0.83.1" +"metro-minify-terser@npm:0.84.5": + version: 0.84.5 + resolution: "metro-minify-terser@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" terser: "npm:^5.15.0" - checksum: 10c0/43196d7084a4664b6f49a09dfb96f4e54b3aec94191891266c664f66f07be76e8b6f616fd2e0205af3b01dc82a214e4d5f865185fd9534703cd90fb80c065c11 + checksum: 10c0/0fb7f2882d66698110027565a8a4ccd099d28df660ac3853a328575b457b24e2eb0a5ab06a000464fc86f25da34502e60cc1314c5decb90c98098378f90386d2 languageName: node linkType: hard -"metro-minify-terser@npm:0.83.2": - version: 0.83.2 - resolution: "metro-minify-terser@npm:0.83.2" +"metro-minify-terser@npm:0.84.6": + version: 0.84.6 + resolution: "metro-minify-terser@npm:0.84.6" dependencies: flow-enums-runtime: "npm:^0.0.6" terser: "npm:^5.15.0" - checksum: 10c0/0bf1cb557d30c82b701c2cb8a89f92c93ebb54891bd88dc3a504783e2038825bbde77095b0c9ff74069e09b170742b8fe37f5f9233e421a41c997fe5b3303d66 + checksum: 10c0/1d29d51758c4fe1d37472602e7c36b49a352bfedd027323c08e02a6f195837c63989c1813aca898910f33a41ab18a8709c75fa59b566af35c9f346e44402a429 languageName: node linkType: hard -"metro-resolver@npm:0.83.1": - version: 0.83.1 - resolution: "metro-resolver@npm:0.83.1" +"metro-resolver@npm:0.84.5": + version: 0.84.5 + resolution: "metro-resolver@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/33d711834f962d5c1c24c41826604f69628da019e816665619dead52f896a1e41afb7eb78ac7a82a3811e9be69def5909e7fee377dd68992faa4b4f71d73b1be + checksum: 10c0/44822149fbd52f680461e94ac7f8bf0855461607ed8d286a5ad615b8bd4a6b113cf0f3c2c403285514c6e1f46cfe7c95e52d9b59633506701dee735b6e788599 languageName: node linkType: hard -"metro-resolver@npm:0.83.2": - version: 0.83.2 - resolution: "metro-resolver@npm:0.83.2" +"metro-resolver@npm:0.84.6": + version: 0.84.6 + resolution: "metro-resolver@npm:0.84.6" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/1094ffa21f8f5273cd0f43d663806a62ed45841c7b4e51f1a823437e0659a98e13829ab18e419e2cfb3cbd097e0263c75e7937a92c5da69718458adec6c4ca51 + checksum: 10c0/318cccc86d6e51ccc45f5cf369d98ee620d4090fb66bf4fefb1edaa287a6c19073d87228b3527147073985690773d8d89ab91026cf0801992284025385072d78 languageName: node linkType: hard -"metro-runtime@npm:0.83.1": - version: 0.83.1 - resolution: "metro-runtime@npm:0.83.1" +"metro-runtime@npm:0.84.5": + version: 0.84.5 + resolution: "metro-runtime@npm:0.84.5" dependencies: "@babel/runtime": "npm:^7.25.0" flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/f9d83d2be8c0ee4a9fded075a8154249e94b156fd2968d69b6cf7bfb73ff4a6181af5f786485cc1348bb57b963f2b35f6599d4d186c98de6ff3247f3eac47d74 + checksum: 10c0/8f4de088ed0946b153d4afb51bb0e1dc7a36d276821ad7ee81fdbddcc72a7a8391f49d044c351c377387dbb955654078843a8fd11d3a3e41be4017d5d59a0848 languageName: node linkType: hard -"metro-runtime@npm:0.83.2, metro-runtime@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-runtime@npm:0.83.2" +"metro-runtime@npm:0.84.6, metro-runtime@npm:^0.84.3": + version: 0.84.6 + resolution: "metro-runtime@npm:0.84.6" dependencies: "@babel/runtime": "npm:^7.25.0" flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/1eb13af44f47bc490ed663d6ad3024c95cfbc4117c281e60adbb5cd56e3813e3e9e4344b2e2b2e8db517411f3f7a18dadd71c96a7f79b7d019c6326221648564 + checksum: 10c0/44f6287ef8a2e816102301b08c450f38dad7e2580dc5c9b86f6449bf942fbd8880e5ffc04129327aa26e51e57c6a1bba53606feb289f6c789bfd5bfe006ae718 languageName: node linkType: hard @@ -10654,182 +10625,178 @@ __metadata: languageName: node linkType: hard -"metro-source-map@npm:0.83.1": - version: 0.83.1 - resolution: "metro-source-map@npm:0.83.1" +"metro-source-map@npm:0.84.5": + version: 0.84.5 + resolution: "metro-source-map@npm:0.84.5" dependencies: - "@babel/traverse": "npm:^7.25.3" - "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3" - "@babel/types": "npm:^7.25.2" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-symbolicate: "npm:0.83.1" + metro-symbolicate: "npm:0.84.5" nullthrows: "npm:^1.1.1" - ob1: "npm:0.83.1" + ob1: "npm:0.84.5" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" - checksum: 10c0/32d4e367ee029c94559883de70fd6d0a3b4bfa26ea1af26012cf3545bf77d68c12deb314b8717ef9678884f7c56693e63c944e636eff5e37dc66018dbc764549 + checksum: 10c0/9aedfc28c2f04b41bf68844b6daa4141455e9b748b5651384d5eea22b3c65457aaa3470a845f2b464cb3a2d9c16e1a8fd5b04a2347ea6241d5c9912b3b9755bd languageName: node linkType: hard -"metro-source-map@npm:0.83.2, metro-source-map@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-source-map@npm:0.83.2" +"metro-source-map@npm:0.84.6, metro-source-map@npm:^0.84.3": + version: 0.84.6 + resolution: "metro-source-map@npm:0.84.6" dependencies: - "@babel/traverse": "npm:^7.25.3" - "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3" - "@babel/types": "npm:^7.25.2" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-symbolicate: "npm:0.83.2" + metro-symbolicate: "npm:0.84.6" nullthrows: "npm:^1.1.1" - ob1: "npm:0.83.2" + ob1: "npm:0.84.6" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" - checksum: 10c0/1590e9e50389f607dff1283fa70a770dfce1be44299062e8a538f0dd75b63307ea8661495ca64ad9994898e3dd0d1dc995023ddd9b67dd304de3b259a105056c + checksum: 10c0/8cae2e68b69bf34744f4a1c1cc3df7498c664ccb77bbf7a6777691ba7f378a594387374f55a3c4236ad0363a72fe7b1a8600100bbdab9b38954e2992d1281fb0 languageName: node linkType: hard -"metro-symbolicate@npm:0.83.1": - version: 0.83.1 - resolution: "metro-symbolicate@npm:0.83.1" +"metro-symbolicate@npm:0.84.5": + version: 0.84.5 + resolution: "metro-symbolicate@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-source-map: "npm:0.83.1" + metro-source-map: "npm:0.84.5" nullthrows: "npm:^1.1.1" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" bin: metro-symbolicate: src/index.js - checksum: 10c0/6993b48ac6d68a3e7d9c7136504b56c6e7f1def20e3d841d2c01773843807f7bdb0f45e0fbf25d0fcb7c7400cb0273de32fb88ecafa79c7b3cfcac5b3fb254f6 + checksum: 10c0/75defd592eea1a4b6ef5e0e1610bb21188764b9d56ccf52b16031524377ac9127d7af66dbc259bc85b1fe8afa9cd51b677c97a4c1d0fcc37d4494a08656f5fe6 languageName: node linkType: hard -"metro-symbolicate@npm:0.83.2": - version: 0.83.2 - resolution: "metro-symbolicate@npm:0.83.2" +"metro-symbolicate@npm:0.84.6": + version: 0.84.6 + resolution: "metro-symbolicate@npm:0.84.6" dependencies: flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-source-map: "npm:0.83.2" + metro-source-map: "npm:0.84.6" nullthrows: "npm:^1.1.1" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" bin: metro-symbolicate: src/index.js - checksum: 10c0/0c021d63520a9f8daaa70165d42c3050f46de31a02ed372aef19ca3027c5beddb14cf9623942142ff493680c094057d7314f2d712419231f3ff3dcf77f18f790 + checksum: 10c0/aaae6a7db27d9a9f5961726a00c102fbfe16bf6be8b6ddb8fbae0e7fe0a760d252266c492c707094fe289ed34eef27122707cc894170cc5b98be6845c1533874 languageName: node linkType: hard -"metro-transform-plugins@npm:0.83.1": - version: 0.83.1 - resolution: "metro-transform-plugins@npm:0.83.1" +"metro-transform-plugins@npm:0.84.5": + version: 0.84.5 + resolution: "metro-transform-plugins@npm:0.84.5" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.3" + "@babel/generator": "npm:^7.29.1" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" nullthrows: "npm:^1.1.1" - checksum: 10c0/06efcd0a0fd312fecead9679e74c02de27309948bb119404c333c5f22c6ff7c2b081e11b412d65e070ed712059eca6904fe5bbf32c98f1298300f6e1da4fdd5b + checksum: 10c0/3ef1e0c487c6b91b1f9eacfffb2f152094d67fb1b0d536030dd7a02d95da881acb19f0d03c0c4cffc7bca6e3d57538559e1d6fea9e610fe88946c2c0d9c1b9d7 languageName: node linkType: hard -"metro-transform-plugins@npm:0.83.2": - version: 0.83.2 - resolution: "metro-transform-plugins@npm:0.83.2" +"metro-transform-plugins@npm:0.84.6": + version: 0.84.6 + resolution: "metro-transform-plugins@npm:0.84.6" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.3" + "@babel/generator": "npm:^7.29.1" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" nullthrows: "npm:^1.1.1" - checksum: 10c0/55925ace9b878721b478f0c0e95abdbd7d834c4738611a6b5c4a3e457a0f01c81b17356c3158fd70960c7f01b43ff641de2b2ad28888afaac21d73c541820ee1 + checksum: 10c0/06ad6021f0f07771d8a984a0c9302b2833a169654f4582647e60f2b8098652acd4a7811a58b7a4a2575560a709f48a6f9c6524b67037d6061c73ac58f2189625 languageName: node linkType: hard -"metro-transform-worker@npm:0.83.1": - version: 0.83.1 - resolution: "metro-transform-worker@npm:0.83.1" +"metro-transform-worker@npm:0.84.5": + version: 0.84.5 + resolution: "metro-transform-worker@npm:0.84.5" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" - "@babel/types": "npm:^7.25.2" + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" - metro: "npm:0.83.1" - metro-babel-transformer: "npm:0.83.1" - metro-cache: "npm:0.83.1" - metro-cache-key: "npm:0.83.1" - metro-minify-terser: "npm:0.83.1" - metro-source-map: "npm:0.83.1" - metro-transform-plugins: "npm:0.83.1" + metro: "npm:0.84.5" + metro-babel-transformer: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-cache-key: "npm:0.84.5" + metro-minify-terser: "npm:0.84.5" + metro-source-map: "npm:0.84.5" + metro-transform-plugins: "npm:0.84.5" nullthrows: "npm:^1.1.1" - checksum: 10c0/b2bcca2664aef4d8dc84ca2ffcf495cf77e4a0d94af1baf41d95226b30440263e75653b02fedff49db329918372a14804e20aed473cf7c7a4104cacf0fa907cd + checksum: 10c0/b4f147f97555feb0fe91114b7a49e8345a8065d88739ff217d26d17bc56fe707b2b1ed0533432dd27aa7169bd7542709ffee5edcfee7dc999c276cc891fbee7d languageName: node linkType: hard -"metro-transform-worker@npm:0.83.2": - version: 0.83.2 - resolution: "metro-transform-worker@npm:0.83.2" +"metro-transform-worker@npm:0.84.6": + version: 0.84.6 + resolution: "metro-transform-worker@npm:0.84.6" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" - "@babel/types": "npm:^7.25.2" + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" - metro: "npm:0.83.2" - metro-babel-transformer: "npm:0.83.2" - metro-cache: "npm:0.83.2" - metro-cache-key: "npm:0.83.2" - metro-minify-terser: "npm:0.83.2" - metro-source-map: "npm:0.83.2" - metro-transform-plugins: "npm:0.83.2" + metro: "npm:0.84.6" + metro-babel-transformer: "npm:0.84.6" + metro-cache: "npm:0.84.6" + metro-cache-key: "npm:0.84.6" + metro-minify-terser: "npm:0.84.6" + metro-source-map: "npm:0.84.6" + metro-transform-plugins: "npm:0.84.6" nullthrows: "npm:^1.1.1" - checksum: 10c0/ea8a0e6bdf24dc5719edb0f2aae0e5bf70f6801cebed8a952fd2d3a1eec95640ac1176d0132faa878897b524ff9828a07810ff22e8a82f007f235d470b027e93 + checksum: 10c0/569d3482631a4378740e3bf354535e697e33835bdbb920f698be4560bc0384254cdad61274ee290bc933871de3cd519890efa29867fe699079b0d5a3ff8ecf6e languageName: node linkType: hard -"metro@npm:0.83.1": - version: 0.83.1 - resolution: "metro@npm:0.83.1" +"metro@npm:0.84.5": + version: 0.84.5 + resolution: "metro@npm:0.84.5" dependencies: - "@babel/code-frame": "npm:^7.24.7" + "@babel/code-frame": "npm:^7.29.0" "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.3" - "@babel/types": "npm:^7.25.2" - accepts: "npm:^1.3.7" - chalk: "npm:^4.0.0" + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + accepts: "npm:^2.0.0" ci-info: "npm:^2.0.0" connect: "npm:^3.6.5" debug: "npm:^4.4.0" error-stack-parser: "npm:^2.0.6" flow-enums-runtime: "npm:^0.0.6" graceful-fs: "npm:^4.2.4" - hermes-parser: "npm:0.29.1" - image-size: "npm:^1.0.2" + hermes-parser: "npm:0.35.0" invariant: "npm:^2.2.4" jest-worker: "npm:^29.7.0" jsc-safe-url: "npm:^0.2.2" lodash.throttle: "npm:^4.1.1" - metro-babel-transformer: "npm:0.83.1" - metro-cache: "npm:0.83.1" - metro-cache-key: "npm:0.83.1" - metro-config: "npm:0.83.1" - metro-core: "npm:0.83.1" - metro-file-map: "npm:0.83.1" - metro-resolver: "npm:0.83.1" - metro-runtime: "npm:0.83.1" - metro-source-map: "npm:0.83.1" - metro-symbolicate: "npm:0.83.1" - metro-transform-plugins: "npm:0.83.1" - metro-transform-worker: "npm:0.83.1" - mime-types: "npm:^2.1.27" + metro-babel-transformer: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-cache-key: "npm:0.84.5" + metro-config: "npm:0.84.5" + metro-core: "npm:0.84.5" + metro-file-map: "npm:0.84.5" + metro-resolver: "npm:0.84.5" + metro-runtime: "npm:0.84.5" + metro-source-map: "npm:0.84.5" + metro-symbolicate: "npm:0.84.5" + metro-transform-plugins: "npm:0.84.5" + metro-transform-worker: "npm:0.84.5" + mime-types: "npm:^3.0.1" nullthrows: "npm:^1.1.1" serialize-error: "npm:^2.1.0" source-map: "npm:^0.5.6" @@ -10838,48 +10805,46 @@ __metadata: yargs: "npm:^17.6.2" bin: metro: src/cli.js - checksum: 10c0/63681a43f7e6d8f1998b99e94bc41998726e3f5f74ebdfe37e127a38c2cc9b796c1c29407d64660d6f7819a7cfa9cb9881c0620678aa8c068c45e69d86aa3b2c + checksum: 10c0/be9981927748f56c2f50f10d1de06e11917421718f3107dad95ee149892a8e0fb91bddc0b17f091984259c1b41bb01a193dc6280785ae6e4a5ad804fbd61c6d2 languageName: node linkType: hard -"metro@npm:0.83.2, metro@npm:^0.83.1": - version: 0.83.2 - resolution: "metro@npm:0.83.2" +"metro@npm:0.84.6, metro@npm:^0.84.3": + version: 0.84.6 + resolution: "metro@npm:0.84.6" dependencies: - "@babel/code-frame": "npm:^7.24.7" + "@babel/code-frame": "npm:^7.29.0" "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.3" - "@babel/types": "npm:^7.25.2" - accepts: "npm:^1.3.7" - chalk: "npm:^4.0.0" + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + accepts: "npm:^2.0.0" ci-info: "npm:^2.0.0" connect: "npm:^3.6.5" debug: "npm:^4.4.0" error-stack-parser: "npm:^2.0.6" flow-enums-runtime: "npm:^0.0.6" graceful-fs: "npm:^4.2.4" - hermes-parser: "npm:0.32.0" - image-size: "npm:^1.0.2" + hermes-parser: "npm:0.35.0" invariant: "npm:^2.2.4" jest-worker: "npm:^29.7.0" jsc-safe-url: "npm:^0.2.2" lodash.throttle: "npm:^4.1.1" - metro-babel-transformer: "npm:0.83.2" - metro-cache: "npm:0.83.2" - metro-cache-key: "npm:0.83.2" - metro-config: "npm:0.83.2" - metro-core: "npm:0.83.2" - metro-file-map: "npm:0.83.2" - metro-resolver: "npm:0.83.2" - metro-runtime: "npm:0.83.2" - metro-source-map: "npm:0.83.2" - metro-symbolicate: "npm:0.83.2" - metro-transform-plugins: "npm:0.83.2" - metro-transform-worker: "npm:0.83.2" - mime-types: "npm:^2.1.27" + metro-babel-transformer: "npm:0.84.6" + metro-cache: "npm:0.84.6" + metro-cache-key: "npm:0.84.6" + metro-config: "npm:0.84.6" + metro-core: "npm:0.84.6" + metro-file-map: "npm:0.84.6" + metro-resolver: "npm:0.84.6" + metro-runtime: "npm:0.84.6" + metro-source-map: "npm:0.84.6" + metro-symbolicate: "npm:0.84.6" + metro-transform-plugins: "npm:0.84.6" + metro-transform-worker: "npm:0.84.6" + mime-types: "npm:^3.0.1" nullthrows: "npm:^1.1.1" serialize-error: "npm:^2.1.0" source-map: "npm:^0.5.6" @@ -10888,7 +10853,7 @@ __metadata: yargs: "npm:^17.6.2" bin: metro: src/cli.js - checksum: 10c0/4c3cc7c2a455471d05757b567a0f2ca604a33f55ffbf2d838dbba8519396f2514b00d6a9214af288343be0277655e3397f2e7816506ec41aed16a9fa54585018 + checksum: 10c0/04e1b9f6f234795c9274b9b68b0690d62bce285c2bfcc4f7deddc217a97882b85d56db1c8cd285fda2b0a67b1fb81255a7469496fcc49e48d7c8bcc6a7f8dac8 languageName: node linkType: hard @@ -10916,7 +10881,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:3.0.2": +"mime-types@npm:3.0.2, mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -10925,7 +10890,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.27, mime-types@npm:^2.1.35, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.35, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -10989,7 +10954,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.0, minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -10998,7 +10963,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.8": +"minimist@npm:^1.2.5, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 @@ -11111,6 +11076,13 @@ __metadata: languageName: node linkType: hard +"multitars@npm:^1.0.2": + version: 1.0.2 + resolution: "multitars@npm:1.0.2" + checksum: 10c0/3ba8805a66bcdb95a9841e13b59370bd737777a0435fed661e321af89e6f235ef286ca5b5d7992c4f079795826e8734e9aa7db368e389bc7a008550b50d928d2 + languageName: node + linkType: hard + "mute-stream@npm:^3.0.0": version: 3.0.0 resolution: "mute-stream@npm:3.0.0" @@ -11138,12 +11110,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.7": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" +"nanoid@npm:^3.3.18": + version: 3.3.18 + resolution: "nanoid@npm:3.3.18" bin: nanoid: bin/nanoid.cjs - checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + checksum: 10c0/b994b4e396730f8be2520923284e2040d61eaee55cc6d4935ef6d38d34bafdc46133eda4d3faea5073bda545aa6079d82b886caeac5c731cf9ac18bcc1301425 languageName: node linkType: hard @@ -11182,13 +11154,6 @@ __metadata: languageName: node linkType: hard -"nested-error-stacks@npm:~2.0.1": - version: 2.0.1 - resolution: "nested-error-stacks@npm:2.0.1" - checksum: 10c0/125049632bc3ca2252e994ca07f27d795c0e6decc4077f0f4163348d30d7cb95409ceff6184284c95396aa5ea8ff5010673063db7674058b966b4f0228d4981c - languageName: node - linkType: hard - "netmask@npm:^2.0.2": version: 2.0.2 resolution: "netmask@npm:2.0.2" @@ -11226,7 +11191,7 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1.2.1, node-forge@npm:^1.3.1": +"node-forge@npm:^1.3.3": version: 1.4.0 resolution: "node-forge@npm:1.4.0" checksum: 10c0/67330a5f1f95257a4c8a93b7d555abe87b5f15e350123aa396c97a21a8ca94f9c6549008eb2c73668a91e0d7e3a905785acbd8f8bd0751c29401292011f8f8e1 @@ -11353,21 +11318,21 @@ __metadata: languageName: node linkType: hard -"ob1@npm:0.83.1": - version: 0.83.1 - resolution: "ob1@npm:0.83.1" +"ob1@npm:0.84.5": + version: 0.84.5 + resolution: "ob1@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/95b13a29239741b2e177459e25404b26ae096f11ac75711f530eda00503492707930354cb78a3d8ffbc2d6d5c3e55165955b4cb50ad08dfe0ae12f138b37a75a + checksum: 10c0/f00c3bdb8c8b53de03a3352a062e170e1ba1cf1977b3dfab492882a0f6fa9eccb5503dde012b1ef7d24c5060f5a6439457e850b98bd4148e2578ffa650b414b8 languageName: node linkType: hard -"ob1@npm:0.83.2": - version: 0.83.2 - resolution: "ob1@npm:0.83.2" +"ob1@npm:0.84.6": + version: 0.84.6 + resolution: "ob1@npm:0.84.6" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/fadcdb9e801458ebc66a09554d3535978e031e1c29e1d450f64f4d8da53ba1505b029726349a3211c5d388af49d1e8bb4f50304526ca237cb2b861177eeea327 + checksum: 10c0/f65a3afcbf8a31df1f7e9cf8bf92dd1579e2993fbe308bda570d09eb11b51aac3ce74da15b8e3ae0c900edd83ed58afedafceaa6e5a6c8e27f83b39fc25b66b9 languageName: node linkType: hard @@ -11470,17 +11435,6 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.4": - version: 8.4.2 - resolution: "open@npm:8.4.2" - dependencies: - define-lazy-prop: "npm:^2.0.0" - is-docker: "npm:^2.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -11647,16 +11601,6 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-json@npm:4.0.0" - dependencies: - error-ex: "npm:^1.3.1" - json-parse-better-errors: "npm:^1.0.1" - checksum: 10c0/8d80790b772ccb1bcea4e09e2697555e519d83d04a77c2b4237389b813f82898943a93ffff7d0d2406203bdd0c30dcf95b1661e3a53f83d0e417f053957bef32 - languageName: node - linkType: hard - "parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -11752,7 +11696,7 @@ __metadata: languageName: node linkType: hard -"path-parse@npm:^1.0.5, path-parse@npm:^1.0.7": +"path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 @@ -11814,13 +11758,6 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^3.0.1": - version: 3.0.1 - resolution: "picomatch@npm:3.0.1" - checksum: 10c0/70ec738569f1864658378b7abdab8939d15dae0718c1df994eae3346fd33daf6a3c1ff4e0c1a0cd1e2c0319130985b63a2cff34d192f2f2acbb78aca76111736 - languageName: node - linkType: hard - "picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": version: 4.0.3 resolution: "picomatch@npm:4.0.3" @@ -11828,6 +11765,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.4": + version: 4.0.7 + resolution: "picomatch@npm:4.0.7" + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f + languageName: node + linkType: hard + "pirates@npm:^4.0.1, pirates@npm:^4.0.4": version: 4.0.7 resolution: "pirates@npm:4.0.7" @@ -11891,14 +11835,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:~8.4.32": - version: 8.4.49 - resolution: "postcss@npm:8.4.49" +"postcss@npm:^8.5.14": + version: 8.5.28 + resolution: "postcss@npm:8.5.28" dependencies: - nanoid: "npm:^3.3.7" + nanoid: "npm:^3.3.18" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3 + checksum: 10c0/9fe44215a6628d89c8a75184b92f5b2f77e16f9e5b13b39b9009bb7e8e6eccddf82c8854bae922db1f17ace6ef3445073fe38abff513caeb03e5285b1b55620a languageName: node linkType: hard @@ -11948,13 +11892,6 @@ __metadata: languageName: node linkType: hard -"pretty-bytes@npm:^5.6.0": - version: 5.6.0 - resolution: "pretty-bytes@npm:5.6.0" - checksum: 10c0/f69f494dcc1adda98dbe0e4a36d301e8be8ff99bfde7a637b2ee2820e7cb583b0fc0f3a63b0e3752c01501185a5cf38602c7be60da41bdf84ef5b70e89c370f3 - languageName: node - linkType: hard - "pretty-format@npm:30.0.5, pretty-format@npm:^30.0.0, pretty-format@npm:^30.0.5": version: 30.0.5 resolution: "pretty-format@npm:30.0.5" @@ -12089,15 +12026,6 @@ __metadata: languageName: node linkType: hard -"qrcode-terminal@npm:0.11.0": - version: 0.11.0 - resolution: "qrcode-terminal@npm:0.11.0" - bin: - qrcode-terminal: ./bin/qrcode-terminal.js - checksum: 10c0/7561a649d21d7672d451ada5f2a2b393f586627cea75670c97141dc2b4b4145db547e1fddf512a3552e7fb54de530d513a736cd604c840adb908ed03c32312ad - languageName: node - linkType: hard - "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -12112,15 +12040,6 @@ __metadata: languageName: node linkType: hard -"queue@npm:6.0.2": - version: 6.0.2 - resolution: "queue@npm:6.0.2" - dependencies: - inherits: "npm:~2.0.3" - checksum: 10c0/cf987476cc72e7d3aaabe23ccefaab1cd757a2b5e0c8d80b67c9575a6b5e1198807ffd4f0948a3f118b149d1111d810ee773473530b77a5c606673cac2c9c996 - languageName: node - linkType: hard - "quickjs-wasi@npm:^0.0.1": version: 0.0.1 resolution: "quickjs-wasi@npm:0.0.1" @@ -12145,20 +12064,6 @@ __metadata: languageName: node linkType: hard -"rc@npm:~1.2.7": - version: 1.2.8 - resolution: "rc@npm:1.2.8" - dependencies: - deep-extend: "npm:^0.6.0" - ini: "npm:~1.3.0" - minimist: "npm:^1.2.0" - strip-json-comments: "npm:~2.0.1" - bin: - rc: ./cli.js - checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 - languageName: node - linkType: hard - "react-devtools-core@npm:^6.1.5": version: 6.1.5 resolution: "react-devtools-core@npm:6.1.5" @@ -12169,14 +12074,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:19.1.0": - version: 19.1.0 - resolution: "react-dom@npm:19.1.0" +"react-dom@npm:19.2.3": + version: 19.2.3 + resolution: "react-dom@npm:19.2.3" dependencies: - scheduler: "npm:^0.26.0" + scheduler: "npm:^0.27.0" peerDependencies: - react: ^19.1.0 - checksum: 10c0/3e26e89bb6c67c9a6aa86cb888c7a7f8258f2e347a6d2a15299c17eb16e04c19194e3452bc3255bd34000a61e45e2cb51e46292392340432f133e5a5d2dfb5fc + react: ^19.2.3 + checksum: 10c0/dc43f7ede06f46f3acc16ee83107c925530de9b91d1d0b3824583814746ff4c498ea64fd65cd83aba363205268adff52e2827c582634ae7b15069deaeabc4892 languageName: node linkType: hard @@ -12187,13 +12092,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^19.1.0": - version: 19.1.1 - resolution: "react-is@npm:19.1.1" - checksum: 10c0/3dba763fcd69835ae263dcd6727d7ffcc44c1d616f04b7329e67aefdc66a567af4f8dcecdd29454c7a707c968aa1eb85083a83fb616f01675ef25e71cf082f97 - languageName: node - linkType: hard - "react-is@npm:^19.2.3": version: 19.2.7 resolution: "react-is@npm:19.2.7" @@ -12237,17 +12135,21 @@ __metadata: version: 0.0.0-use.local resolution: "react-native-css-example@workspace:example" dependencies: - "@expo/metro-runtime": "npm:~6.1.2" + "@babel/core": "npm:^7.29.0" + "@expo/metro-config": "npm:57.0.12" + "@expo/metro-runtime": "npm:~57.0.15" + "@react-native/metro-config": "npm:0.86.3" "@tailwindcss/postcss": "npm:^4.1.11" - expo: "npm:54.0.10" - expo-status-bar: "npm:~3.0.8" - react: "npm:19.1.0" - react-dom: "npm:19.1.0" - react-native: "npm:0.81.4" + expo: "npm:57.0.22" + expo-status-bar: "npm:~57.0.1" + lightningcss: "npm:^1.30.1" + react: "npm:19.2.3" + react-dom: "npm:19.2.3" + react-native: "npm:0.86.3" react-native-css: "link:../" - react-native-reanimated: "npm:~4.1.0" - react-native-web: "npm:~0.21.1" - react-native-worklets: "npm:~0.5.0" + react-native-reanimated: "npm:4.5.1" + react-native-web: "npm:~0.21.0" + react-native-worklets: "npm:0.10.1" languageName: unknown linkType: soft @@ -12261,11 +12163,13 @@ __metadata: version: 0.0.0-use.local resolution: "react-native-css@workspace:." dependencies: - "@babel/core": "npm:^7.28.0" + "@babel/core": "npm:^7.29.0" "@commitlint/config-conventional": "npm:^20.5.0" "@eslint/js": "npm:^10.0.1" - "@expo/metro-config": "npm:~54.0.5" + "@expo/metro-config": "npm:57.0.12" "@ianvs/prettier-plugin-sort-imports": "npm:^4.4.2" + "@react-native/jest-preset": "npm:0.86.3" + "@react-native/metro-config": "npm:0.86.3" "@release-it/conventional-changelog": "npm:10.0.1" "@tailwindcss/postcss": "npm:^4.1.12" "@testing-library/react-native": "npm:^13.3.3" @@ -12273,11 +12177,11 @@ __metadata: "@types/babel__core": "npm:^7" "@types/debug": "npm:^4.1.12" "@types/jest": "npm:^30.0.0" - "@types/react": "npm:^19.1.10" + "@types/react": "npm:~19.2.0" "@types/react-test-renderer": "npm:^19" babel-plugin-react-compiler: "npm:^19.1.0-rc.2" babel-plugin-tester: "npm:^12.0.0" - babel-preset-expo: "npm:~54.0.3" + babel-preset-expo: "npm:57.0.11" colorjs.io: "npm:0.7.0" comment-json: "npm:^4.2.5" commitlint: "npm:^20.0.0" @@ -12286,26 +12190,26 @@ __metadata: eslint: "npm:^9.30.1" eslint-config-prettier: "npm:^10.1.5" eslint-plugin-prettier: "npm:^5.5.1" - expo: "npm:54.0.10" + expo: "npm:57.0.22" jest: "npm:^29.7.0" - jest-expo: "npm:~56.0.5" + jest-expo: "npm:57.0.5" lefthook: "npm:^2.1.5" lightningcss: "npm:^1.30.1" metro-runtime: "npm:^0.84.2" postcss: "npm:^8.5.6" prettier: "npm:^3.6.2" - react: "npm:19.1.0" - react-native: "npm:0.81.4" + react: "npm:19.2.3" + react-native: "npm:0.86.3" react-native-builder-bob: "npm:^0.43.0" - react-native-reanimated: "npm:~4.1.0" - react-native-safe-area-context: "npm:5.6.1" - react-native-worklets: "npm:~0.5.0" + react-native-reanimated: "npm:4.5.1" + react-native-safe-area-context: "npm:~5.7.0" + react-native-worklets: "npm:0.10.1" react-refresh: "npm:^0.17.0" - react-test-renderer: "npm:19.1.0" + react-test-renderer: "npm:19.2.3" release-it: "npm:^20.2.1" tailwindcss: "npm:^4.1.12" tailwindcss-safe-area: "npm:^1.1.0" - typescript: "npm:^5.9.2" + typescript: "npm:~6.0.3" typescript-eslint: "npm:^8.40.0" peerDependencies: "@expo/metro-config": ">=54" @@ -12315,44 +12219,43 @@ __metadata: languageName: unknown linkType: soft -"react-native-is-edge-to-edge@npm:^1.2.1": - version: 1.2.1 - resolution: "react-native-is-edge-to-edge@npm:1.2.1" +"react-native-is-edge-to-edge@npm:^1.3.1": + version: 1.3.1 + resolution: "react-native-is-edge-to-edge@npm:1.3.1" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/87d20b900aded7d44c90afb946a7aa03c23a94ca3dd547bdddc2303b85357e4aab22567a57b19f1558d6c8be7058e3dcf34faa1e15182d1604f90974266d9a1d + checksum: 10c0/28cebd5f1f3632864ff5e342278721d1e5e38627ae73859a8814012116ef15c629fee7137a6c9c97bb05d94bbe639b0b47e69b36fc2735ab53ed31570140663f languageName: node linkType: hard -"react-native-reanimated@npm:~4.1.0": - version: 4.1.0 - resolution: "react-native-reanimated@npm:4.1.0" +"react-native-reanimated@npm:4.5.1": + version: 4.5.1 + resolution: "react-native-reanimated@npm:4.5.1" dependencies: - react-native-is-edge-to-edge: "npm:^1.2.1" - semver: "npm:7.7.2" + react-native-is-edge-to-edge: "npm:^1.3.1" + semver: "npm:^7.7.3" peerDependencies: - "@babel/core": ^7.0.0-0 react: "*" - react-native: "*" - react-native-worklets: ">=0.5.0" - checksum: 10c0/45a072bfc3a56fc84bf21cb9853aec61622cf5e21be6f3f900988f8c53702cc6b2567380febe63c78166dac5aa50834b29d73dc1a7fdb4b9b950abdad1ef0cc4 + react-native: 0.83 - 0.86 + react-native-worklets: 0.10.x + checksum: 10c0/86c64529fc5d59fb1a9083e0934c957c95c7319149efe02f92252237aa1fb208958da1c42705447c12b1d6bff3c23a354e10a05787995082eda1c284703ec0bc languageName: node linkType: hard -"react-native-safe-area-context@npm:5.6.1": - version: 5.6.1 - resolution: "react-native-safe-area-context@npm:5.6.1" +"react-native-safe-area-context@npm:~5.7.0": + version: 5.7.0 + resolution: "react-native-safe-area-context@npm:5.7.0" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/797ad7d749bd42cbec8e504d969de13e17ed48506c2fd5a639d05d78d88194c21d72b9dc4608e08a2e8edac23341802e7b4661875242dc3bdce3008cfda5bcbe + checksum: 10c0/c3799e17321b41df1e0a10492c98472f8f8225ef0bbaf8146c4a9acb9519aae9ac11429059143c215e4402c2808e8445274850a339f8477522ded2461e18da80 languageName: node linkType: hard -"react-native-web@npm:~0.21.1": - version: 0.21.1 - resolution: "react-native-web@npm:0.21.1" +"react-native-web@npm:~0.21.0": + version: 0.21.2 + resolution: "react-native-web@npm:0.21.2" dependencies: "@babel/runtime": "npm:^7.18.6" "@react-native/normalize-colors": "npm:^0.74.1" @@ -12365,80 +12268,83 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/6a72f32c30909d2e76f721f8257f6bf32d83ec8dec480aebefc549a479b9c87e4ef019d5dadc9d0d21703a9b1801803d51a25227f54cd4ad330ca14676dd5956 + checksum: 10c0/8c184fef0045c25deff765c8e80963454a5dffd8e389a9e11cf2fec9e769ff0f82c3d56d082b1897a7ded8374d9ae8a49dac7f09377a104f1995a5ddea645095 languageName: node linkType: hard -"react-native-worklets@npm:~0.5.0": - version: 0.5.1 - resolution: "react-native-worklets@npm:0.5.1" - dependencies: - "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" - "@babel/plugin-transform-class-properties": "npm:^7.0.0-0" - "@babel/plugin-transform-classes": "npm:^7.0.0-0" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" - "@babel/plugin-transform-optional-chaining": "npm:^7.0.0-0" - "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0-0" - "@babel/plugin-transform-template-literals": "npm:^7.0.0-0" - "@babel/plugin-transform-unicode-regex": "npm:^7.0.0-0" - "@babel/preset-typescript": "npm:^7.16.7" +"react-native-worklets@npm:0.10.1": + version: 0.10.1 + resolution: "react-native-worklets@npm:0.10.1" + dependencies: + "@babel/plugin-transform-arrow-functions": "npm:^7.27.1" + "@babel/plugin-transform-class-properties": "npm:^7.28.6" + "@babel/plugin-transform-classes": "npm:^7.28.6" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.28.6" + "@babel/plugin-transform-optional-chaining": "npm:^7.28.6" + "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" + "@babel/plugin-transform-template-literals": "npm:^7.27.1" + "@babel/plugin-transform-unicode-regex": "npm:^7.27.1" + "@babel/preset-typescript": "npm:^7.28.5" + "@babel/types": "npm:^7.27.1" convert-source-map: "npm:^2.0.0" - semver: "npm:7.7.2" + semver: "npm:^7.7.4" peerDependencies: - "@babel/core": ^7.0.0-0 + "@babel/core": "*" + "@react-native/metro-config": "*" react: "*" - react-native: "*" - checksum: 10c0/9eb9e6dea9abaf889400a6618355ef59af3075f5004a4bec9e4cba6dcfd13d8b63de0d4b29d75c00a3dcf5ad422e1bdb71636c75b1a2ad1c43d8b512f198bdab + react-native: 0.83 - 0.86 + checksum: 10c0/31712cf1b311e3dc96544a665da269cdd6be8cab286234cea968c22509398924020f168b1e9e13a532acc23086048c16f5167aa3737e756711239b6f7521fe5a languageName: node linkType: hard -"react-native@npm:0.81.4": - version: 0.81.4 - resolution: "react-native@npm:0.81.4" +"react-native@npm:0.86.3": + version: 0.86.3 + resolution: "react-native@npm:0.86.3" dependencies: - "@jest/create-cache-key-function": "npm:^29.7.0" - "@react-native/assets-registry": "npm:0.81.4" - "@react-native/codegen": "npm:0.81.4" - "@react-native/community-cli-plugin": "npm:0.81.4" - "@react-native/gradle-plugin": "npm:0.81.4" - "@react-native/js-polyfills": "npm:0.81.4" - "@react-native/normalize-colors": "npm:0.81.4" - "@react-native/virtualized-lists": "npm:0.81.4" + "@react-native/assets-registry": "npm:0.86.3" + "@react-native/codegen": "npm:0.86.3" + "@react-native/community-cli-plugin": "npm:0.86.3" + "@react-native/gradle-plugin": "npm:0.86.3" + "@react-native/js-polyfills": "npm:0.86.3" + "@react-native/normalize-colors": "npm:0.86.3" + "@react-native/virtualized-lists": "npm:0.86.3" abort-controller: "npm:^3.0.0" anser: "npm:^1.4.9" ansi-regex: "npm:^5.0.0" - babel-jest: "npm:^29.7.0" - babel-plugin-syntax-hermes-parser: "npm:0.29.1" + babel-plugin-syntax-hermes-parser: "npm:0.36.0" base64-js: "npm:^1.5.1" commander: "npm:^12.0.0" flow-enums-runtime: "npm:^0.0.6" - glob: "npm:^7.1.1" + hermes-compiler: "npm:250829098.0.17" invariant: "npm:^2.2.4" - jest-environment-node: "npm:^29.7.0" memoize-one: "npm:^5.0.0" - metro-runtime: "npm:^0.83.1" - metro-source-map: "npm:^0.83.1" + metro-runtime: "npm:^0.84.3" + metro-source-map: "npm:^0.84.3" nullthrows: "npm:^1.1.1" pretty-format: "npm:^29.7.0" promise: "npm:^8.3.0" react-devtools-core: "npm:^6.1.5" react-refresh: "npm:^0.14.0" regenerator-runtime: "npm:^0.13.2" - scheduler: "npm:0.26.0" + scheduler: "npm:0.27.0" semver: "npm:^7.1.3" stacktrace-parser: "npm:^0.1.10" + tinyglobby: "npm:^0.2.15" whatwg-fetch: "npm:^3.0.0" - ws: "npm:^6.2.3" + ws: "npm:^7.5.10" yargs: "npm:^17.6.2" peerDependencies: - "@types/react": ^19.1.0 - react: ^19.1.0 + "@react-native/jest-preset": 0.86.3 + "@types/react": ^19.1.1 + react: ^19.2.3 peerDependenciesMeta: + "@react-native/jest-preset": + optional: true "@types/react": optional: true bin: react-native: cli.js - checksum: 10c0/fcac8d18e1b479a0df43577b3c78bf88add430b529c32a649cd7528c6f1661d64eaf99910df48647df910a68a93adb88f3a2d8a9baddf9003ae59b8570d4ab09 + checksum: 10c0/a8354c58be9ca1509c48d4c06194f4be4b902c17ef6f4fc5bdfb05960b079b156b453998c69ed08ee0a4d60b00aeac7eaf8a9cfc428d24bec46304c06063b4c0 languageName: node linkType: hard @@ -12456,18 +12362,6 @@ __metadata: languageName: node linkType: hard -"react-test-renderer@npm:19.1.0": - version: 19.1.0 - resolution: "react-test-renderer@npm:19.1.0" - dependencies: - react-is: "npm:^19.1.0" - scheduler: "npm:^0.26.0" - peerDependencies: - react: ^19.1.0 - checksum: 10c0/34ed4a37ba8b0beb96c048de6ff28574f018a18dd1042c24f8f46142d48eb5b27f82ff7c2823d082932fd3983c5a3529ab8cc8f15191d4306df0082f9f84678f - languageName: node - linkType: hard - "react-test-renderer@npm:19.2.3": version: 19.2.3 resolution: "react-test-renderer@npm:19.2.3" @@ -12480,10 +12374,10 @@ __metadata: languageName: node linkType: hard -"react@npm:19.1.0": - version: 19.1.0 - resolution: "react@npm:19.1.0" - checksum: 10c0/530fb9a62237d54137a13d2cfb67a7db6a2156faed43eecc423f4713d9b20c6f2728b026b45e28fcd72e8eadb9e9ed4b089e99f5e295d2f0ad3134251bdd3698 +"react@npm:19.2.3": + version: 19.2.3 + resolution: "react@npm:19.2.3" + checksum: 10c0/094220b3ba3a76c1b668f972ace1dd15509b157aead1b40391d1c8e657e720c201d9719537375eff08f5e0514748c0319063392a6f000e31303aafc4471f1436 languageName: node linkType: hard @@ -12678,17 +12572,6 @@ __metadata: languageName: node linkType: hard -"requireg@npm:^0.2.2": - version: 0.2.2 - resolution: "requireg@npm:0.2.2" - dependencies: - nested-error-stacks: "npm:~2.0.1" - rc: "npm:~1.2.7" - resolve: "npm:~1.7.1" - checksum: 10c0/806cff08d8fa63f2ec9c74fa9602c86b56627a824d0a188bf777c8d82ba012a1b3c01ab6e88ffcf610713b6bc5ec8a9f9e55dc941b7606ce735e72c4d9daa059 - languageName: node - linkType: hard - "requires-port@npm:^1.0.0": version: 1.0.0 resolution: "requires-port@npm:1.0.0" @@ -12705,13 +12588,6 @@ __metadata: languageName: node linkType: hard -"resolve-from@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-from@npm:3.0.0" - checksum: 10c0/24affcf8e81f4c62f0dcabc774afe0e19c1f38e34e43daac0ddb409d79435fc3037f612b0cc129178b8c220442c3babd673e88e870d27215c99454566e770ebc - languageName: node - linkType: hard - "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -12726,15 +12602,6 @@ __metadata: languageName: node linkType: hard -"resolve-global@npm:^1.0.0": - version: 1.0.0 - resolution: "resolve-global@npm:1.0.0" - dependencies: - global-dirs: "npm:^0.1.1" - checksum: 10c0/fda6ba81a07a0124756ce956dd871ca83763973326d8617143dab38d9c9afc666926604bfe8f0bfd046a9a285347568f32ceb3d4c55a1cb9de5614cca001a21c - languageName: node - linkType: hard - "resolve-workspace-root@npm:^2.0.0": version: 2.0.0 resolution: "resolve-workspace-root@npm:2.0.0" @@ -12742,14 +12609,14 @@ __metadata: languageName: node linkType: hard -"resolve.exports@npm:^2.0.0, resolve.exports@npm:^2.0.3": +"resolve.exports@npm:^2.0.0": version: 2.0.3 resolution: "resolve.exports@npm:2.0.3" checksum: 10c0/1ade1493f4642a6267d0a5e68faeac20b3d220f18c28b140343feb83694d8fed7a286852aef43689d16042c61e2ddb270be6578ad4a13990769e12065191200d languageName: node linkType: hard -"resolve@npm:^1.20.0, resolve@npm:^1.22.10, resolve@npm:^1.22.2": +"resolve@npm:^1.20.0, resolve@npm:^1.22.10": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -12776,16 +12643,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:~1.7.1": - version: 1.7.1 - resolution: "resolve@npm:1.7.1" - dependencies: - path-parse: "npm:^1.0.5" - checksum: 10c0/6e9e29185ac57801aff013849e9717c769ef0a27eac30b6492405ba3d61db73d8967023b96578f4b2deba4ef5fb11fc4f0a4db47c0f536890ced5c014e94fbde - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin": +"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: @@ -12812,15 +12670,6 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A~1.7.1#optional!builtin": - version: 1.7.1 - resolution: "resolve@patch:resolve@npm%3A1.7.1#optional!builtin::version=1.7.1&hash=3bafbf" - dependencies: - path-parse: "npm:^1.0.5" - checksum: 10c0/1301dba7c12cd9dab2ab4eee8518089f25bb7480db34b746a923ded472c4c0600ebb1ba9b8028ca843f7c6017ac76524355800c52b82633e53bd601ca288b4de - languageName: node - linkType: hard - "restore-cursor@npm:^2.0.0": version: 2.0.0 resolution: "restore-cursor@npm:2.0.0" @@ -12862,17 +12711,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 - languageName: node - linkType: hard - "run-applescript@npm:^7.0.0": version: 7.1.0 resolution: "run-applescript@npm:7.1.0" @@ -12903,6 +12741,15 @@ __metadata: languageName: node linkType: hard +"sandbox-cli-detector@npm:^0.2.0": + version: 0.2.0 + resolution: "sandbox-cli-detector@npm:0.2.0" + bin: + sandbox-cli-detector: dist/cli.js + checksum: 10c0/e70964918150079a3183948a8adadcde736823da2ad1135b4ccead2baa8d24d6487ddd222c030f94c9ef54e84bc41aa525a5452ef4cb9239f6f64b2ac03337bc + languageName: node + linkType: hard + "sax@npm:>=0.6.0": version: 1.4.1 resolution: "sax@npm:1.4.1" @@ -12919,14 +12766,7 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:0.26.0, scheduler@npm:^0.26.0": - version: 0.26.0 - resolution: "scheduler@npm:0.26.0" - checksum: 10c0/5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356 - languageName: node - linkType: hard - -"scheduler@npm:^0.27.0": +"scheduler@npm:0.27.0, scheduler@npm:^0.27.0": version: 0.27.0 resolution: "scheduler@npm:0.27.0" checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 @@ -12944,15 +12784,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.7.2, semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": - version: 7.7.2 - resolution: "semver@npm:7.7.2" - bin: - semver: bin/semver.js - checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea - languageName: node - linkType: hard - "semver@npm:7.7.4, semver@npm:^7.6.3": version: 7.7.4 resolution: "semver@npm:7.7.4" @@ -12971,6 +12802,24 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": + version: 7.7.2 + resolution: "semver@npm:7.7.2" + bin: + semver: bin/semver.js + checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + languageName: node + linkType: hard + +"semver@npm:^7.7.3, semver@npm:^7.7.4": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + "send@npm:0.19.0": version: 0.19.0 resolution: "send@npm:0.19.0" @@ -13185,7 +13034,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21": +"source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -13485,13 +13334,6 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:~2.0.1": - version: 2.0.1 - resolution: "strip-json-comments@npm:2.0.1" - checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 - languageName: node - linkType: hard - "structured-headers@npm:^0.4.1": version: 0.4.1 resolution: "structured-headers@npm:0.4.1" @@ -13620,13 +13462,6 @@ __metadata: languageName: node linkType: hard -"temp-dir@npm:~2.0.0": - version: 2.0.0 - resolution: "temp-dir@npm:2.0.0" - checksum: 10c0/b1df969e3f3f7903f3426861887ed76ba3b495f63f6d0c8e1ce22588679d9384d336df6064210fda14e640ed422e2a17d5c40d901f60e161c99482d723f4d309 - languageName: node - linkType: hard - "terminal-link@npm:2.1.1, terminal-link@npm:^2.1.1": version: 2.1.1 resolution: "terminal-link@npm:2.1.1" @@ -13718,6 +13553,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.15": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -13741,6 +13586,13 @@ __metadata: languageName: node linkType: hard +"toqr@npm:^0.1.1": + version: 0.1.1 + resolution: "toqr@npm:0.1.1" + checksum: 10c0/eec346afae2eede8886938992a7eba59f765b3d3a3d5e7ce4984cb25b124e1a3d02531ed1ef3100d60fe443eeb1c7f83ca1fa0bbb04915d67baa5380e7c9eda4 + languageName: node + linkType: hard + "tough-cookie@npm:^4.1.2": version: 4.1.4 resolution: "tough-cookie@npm:4.1.4" @@ -13865,17 +13717,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.9.2": - version: 5.9.2 - resolution: "typescript@npm:5.9.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/cd635d50f02d6cf98ed42de2f76289701c1ec587a363369255f01ed15aaf22be0813226bff3c53e99d971f9b540e0b3cc7583dbe05faded49b1b0bed2f638a18 - languageName: node - linkType: hard - -"typescript@npm:^6.0.3": +"typescript@npm:^6.0.3, typescript@npm:~6.0.3": version: 6.0.3 resolution: "typescript@npm:6.0.3" bin: @@ -13885,17 +13727,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.9.2#optional!builtin": - version: 5.9.2 - resolution: "typescript@patch:typescript@npm%3A5.9.2#optional!builtin::version=5.9.2&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/34d2a8e23eb8e0d1875072064d5e1d9c102e0bdce56a10a25c0b917b8aa9001a9cf5c225df12497e99da107dc379360bc138163c66b55b95f5b105b50578067e - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^6.0.3#optional!builtin": +"typescript@patch:typescript@npm%3A^6.0.3#optional!builtin, typescript@patch:typescript@npm%3A~6.0.3#optional!builtin": version: 6.0.3 resolution: "typescript@patch:typescript@npm%3A6.0.3#optional!builtin::version=6.0.3&hash=5786d5" bin: @@ -13937,13 +13769,6 @@ __metadata: languageName: node linkType: hard -"undici@npm:^6.18.2": - version: 6.21.3 - resolution: "undici@npm:6.21.3" - checksum: 10c0/294da109853fad7a6ef5a172ad0ca3fb3f1f60cf34703d062a5ec967daf69ad8c03b52e6d536c5cba3bb65615769bf08e5b30798915cbccdddaca01045173dda - languageName: node - linkType: hard - "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.1 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" @@ -14007,15 +13832,6 @@ __metadata: languageName: node linkType: hard -"unique-string@npm:~2.0.0": - version: 2.0.0 - resolution: "unique-string@npm:2.0.0" - dependencies: - crypto-random-string: "npm:^2.0.0" - checksum: 10c0/11820db0a4ba069d174bedfa96c588fc2c96b083066fafa186851e563951d0de78181ac79c744c1ed28b51f9d82ac5b8196ff3e4560d0178046ef455d8c2244b - languageName: node - linkType: hard - "universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": version: 7.0.3 resolution: "universal-user-agent@npm:7.0.3" @@ -14197,13 +14013,6 @@ __metadata: languageName: node linkType: hard -"webidl-conversions@npm:^5.0.0": - version: 5.0.0 - resolution: "webidl-conversions@npm:5.0.0" - checksum: 10c0/bf31df332ed11e1114bfcae7712d9ab2c37e7faa60ba32d8fdbee785937c0b012eee235c19d2b5d84f5072db84a160e8d08dd382da7f850feec26a4f46add8ff - languageName: node - linkType: hard - "webidl-conversions@npm:^7.0.0": version: 7.0.0 resolution: "webidl-conversions@npm:7.0.0" @@ -14234,14 +14043,10 @@ __metadata: languageName: node linkType: hard -"whatwg-url-without-unicode@npm:8.0.0-3": - version: 8.0.0-3 - resolution: "whatwg-url-without-unicode@npm:8.0.0-3" - dependencies: - buffer: "npm:^5.4.3" - punycode: "npm:^2.1.1" - webidl-conversions: "npm:^5.0.0" - checksum: 10c0/c27a637ab7d01981b2e2f576fde2113b9c42247500e093d2f5ba94b515d5c86dbcf70e5cad4b21b8813185f21fa1b4846f53c79fa87995293457e28c889cc0fd +"whatwg-url-minimum@npm:^0.1.2": + version: 0.1.2 + resolution: "whatwg-url-minimum@npm:0.1.2" + checksum: 10c0/5437bc4e1f49d89ff58b7565214db4640c4ad5d90de6c61207e0dc766dae9b9894a0ea7b7883b8576524601586705c5dc359681c388d76c18eddb51f50de558f languageName: node linkType: hard @@ -14314,13 +14119,6 @@ __metadata: languageName: node linkType: hard -"wonka@npm:^6.3.2": - version: 6.3.5 - resolution: "wonka@npm:6.3.5" - checksum: 10c0/044fe5ae26c0a32b0a1603cc0ed71ede8c9febe5bb3adab4fad5e088ceee600a84a08d0deb95a72189bbaf0d510282d183b6fb7b6e9837e7a1c9b209f788dd07 - languageName: node - linkType: hard - "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" @@ -14385,15 +14183,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:^6.2.3": - version: 6.2.4 - resolution: "ws@npm:6.2.4" - dependencies: - async-limiter: "npm:~1.0.0" - checksum: 10c0/5c2b9474164f9cb68c7776a1d10b0461c186f3a69bffb1028fca33eba5ab7206a09173fb0b311d6c5a81c8cf148406f8deb0b7d899542ab8ca67407d99717dad - languageName: node - linkType: hard - "ws@npm:^7, ws@npm:^7.5.10": version: 7.5.10 resolution: "ws@npm:7.5.10" @@ -14409,7 +14198,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.18.3": +"ws@npm:^8.11.0, ws@npm:^8.12.1": version: 8.18.3 resolution: "ws@npm:8.18.3" peerDependencies: @@ -14583,15 +14372,6 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.24.6": - version: 3.24.6 - resolution: "zod-to-json-schema@npm:3.24.6" - peerDependencies: - zod: ^3.24.1 - checksum: 10c0/b907ab6d057100bd25a37e5545bf5f0efa5902cd84d3c3ec05c2e51541431a47bd9bf1e5e151a244273409b45f5986d55b26e5d207f98abc5200702f733eb368 - languageName: node - linkType: hard - "zod@npm:^3.25.76": version: 3.25.76 resolution: "zod@npm:3.25.76" From fb69c77c51eebfb23229c38cb8a49c87aff17f9d Mon Sep 17 00:00:00 2001 From: Dan Stepanov Date: Fri, 11 Sep 2026 18:43:22 -0700 Subject: [PATCH 2/3] docs: record Expo 57 engine RC verification and known limitation --- docs/expo57-rc.md | 21 +++++++++++++++++++++ docs/known-issues.md | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 docs/expo57-rc.md diff --git a/docs/expo57-rc.md b/docs/expo57-rc.md new file mode 100644 index 00000000..c2a000a6 --- /dev/null +++ b/docs/expo57-rc.md @@ -0,0 +1,21 @@ +# react-native-css Expo 57 release candidate + +Publication draft. The proposed engine is react-native-css 3.1.0-rc.0, paired with Nativewind 5.0.0-rc.0. The packages are not published yet. Nativewind requires this exact engine candidate. + +The audited target is Expo 57.0.22, React Native 0.86.3, React 19.2.3, Reanimated 4.5.1 and Worklets 0.10.1. Keep native dependency versions aligned with Expo. Native dependency changes require rebuilding the application. + +After publication, install the exact engine and Nativewind pair: + +```sh +npm install --save-exact nativewind@5.0.0-rc.0 react-native-css@3.1.0-rc.0 +``` + +The engine source used to prepare the archive is commit 06b7bda3cc04b76c62715e537c371ad16869ec8b. The archive SHA256 is a03235e206ef49e48fb960e59eebea14bbb69e623aa168250d8ccca2ff200072. This documentation commit does not change the audited package contents. + +The RC updates Expo integration, Node ESM tooling exports, TypeScript setup, style and prop mappings, variable resolution, compiler behavior, component identities and cache invalidation. Each accepted contract is associated with explicit expected values and deliberately incorrect controls in the compatibility audit. The full library run passed 1434 tests across the two libraries plus 42 engine Node tests. The release verifier passed 670 tests with no skips. The reviewed inventory accounts for 6129 entries and requires 4985 execution cells. All 4985 passed the final release checker, with zero missing assertions. Public source review and a subsequent publication instruction remain necessary before npm release. + +Android animation cancellation remains affected by [Reanimated issue 10507](https://github.com/software-mansion/react-native-reanimated/issues/10507), which also reproduces without react-native-css or Nativewind. The exact Android animate-none reset case is retained as an accepted upstream defect and excluded from passing support claims. Its isolated result can pass, so the cancellation behavior remains intermittent. iPhone and browser cancellation and every other motion requirement remain independently verified. No Reanimated patch is bundled. Physical Android verification was waived; a Release emulator and physical iPhone are required. Browser object fitting in the original React Native Web and Expo Image adapters requires explicit component props. Some browser selection, backface and fragmentation examples remain explicitly limited. These are not universal CSS support claims. + +Use React Native Appearance and useColorScheme for native dark mode. Appearance.setColorScheme('unspecified') restores the system preference on this Expo target. The full [Nativewind RC compatibility guide](https://github.com/nativewind/nativewind/blob/danstepanov/expo-57-rc-review/docs/rc-compatibility.md) records supported value domains, migration requirements and exact platform limitations. The [installation guide](https://github.com/nativewind/nativewind/blob/danstepanov/expo-57-rc-review/docs/expo57-rc.md) includes Tailwind, PostCSS, Metro and Babel configuration. + +Report problems with exact package and Expo versions, platform, build mode, configuration and a minimal reproduction. Include whether a direct React Native or Reanimated reference also fails. The public v4 to v5 migration skill follows RC publication and must be verified against the published packages before stable promotion. Stable npm tags stay unchanged during the RC release. diff --git a/docs/known-issues.md b/docs/known-issues.md index e931d35c..4111a708 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -1,9 +1,9 @@ # Known Expo 57 dependency limitation -With Expo 57.0.21, React Native 0.86.3, Reanimated 4.5.1, and Worklets 0.10.1, cancelling a CSS animation on Android can leave the component at its last animated transform. A spinning view can remain tilted after switching to `animationName: "none"` or removing the animation styles. In Nativewind, this affects changing `animate-spin` to `animate-none`. +With Expo 57.0.22, React Native 0.86.3, Reanimated 4.5.1, and Worklets 0.10.1, cancelling a CSS animation on Android can leave the component at its last animated transform. A spinning view can remain tilted after switching to `animationName: "none"` or removing the animation styles. In Nativewind, this affects changing `animate-spin` to `animate-none`. -The failure reproduces with a direct Reanimated `Animated.View` without Nativewind or react-native-css. It is tracked in [Reanimated #10507](https://github.com/software-mansion/react-native-reanimated/issues/10507). The confirmed environment is an Android API 34 emulator with Fabric, Hermes, and a Release build. The integrated physical iPhone cancellation case passed. Later Reanimated versions and physical Android have not been verified. +The original Expo 57.0.21 diagnostic reproduced both forms. Fresh Expo 57.0.22 checks reproduce direct style removal and integrated animate-none failure, while isolated none checks can pass. The behavior is intermittent. The failure reproduces with a direct Reanimated `Animated.View` without Nativewind or react-native-css. It is tracked in [Reanimated #10507](https://github.com/software-mansion/react-native-reanimated/issues/10507). The confirmed environment is an Android API 34 emulator with Fabric, Hermes, and a Release build. The integrated physical iPhone cancellation case passed. Later Reanimated versions and physical Android have not been verified. The planned RC retains Expo's exact dependency versions and discloses this limitation. Neither library includes the experimental Reanimated patch. Applications relying on CSS animation cancellation must account for this known behavior. No production workaround is currently verified by this release effort. -The issue includes a [standalone reproduction](https://gist.github.com/danstepanov/03d34ece59f03628deb77a028e8a9a03). When an official fix becomes available in the supported Expo environment, rerun the cancellation and motion checks before removing this notice. This notice does not claim that the RC has been published or that the rest of its release gate is complete. +The issue includes a [standalone reproduction](https://gist.github.com/danstepanov/03d34ece59f03628deb77a028e8a9a03). When an official fix becomes available in the supported Expo environment, rerun the cancellation and motion checks before removing this notice. The full RC gate is complete as described in [the release guide](expo57-rc.md). The packages have not been published. From 5d45e60bd2659ae220bb8e6fb48273ad9c19fc1e Mon Sep 17 00:00:00 2001 From: Dan Stepanov Date: Sat, 12 Sep 2026 19:59:26 -0700 Subject: [PATCH 3/3] ci: fix Expo 57 builds and prepare Git dependencies --- .github/actions/ios-dev-app/action.yml | 23 ++++++++++++++++++++--- .github/workflows/ci.yml | 7 ++++++- package.json | 1 + 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/actions/ios-dev-app/action.yml b/.github/actions/ios-dev-app/action.yml index 1b4e83f6..af5bd11b 100644 --- a/.github/actions/ios-dev-app/action.yml +++ b/.github/actions/ios-dev-app/action.yml @@ -4,15 +4,19 @@ description: Create IOS Development App runs: using: composite steps: + - name: Verify Xcode + shell: bash + run: xcodebuild -version + - name: Cache iOS development build id: ios-dev-cache uses: actions/cache@v4 with: path: | example/ios/build/DerivedData - key: ios-dev-${{ runner.os }}-${{ hashFiles('example/ios/**', 'example/package.json', 'package.json') }} + key: ios-dev-${{ runner.os }}-${{ runner.arch }}-xcode26.6-${{ hashFiles('example/ios/**', 'example/package.json', 'package.json', 'yarn.lock') }} restore-keys: | - ios-dev-${{ runner.os }}- + ios-dev-${{ runner.os }}-${{ runner.arch }}-xcode26.6- - name: Check for cached build shell: bash @@ -55,9 +59,11 @@ runs: -scheme example \ -configuration Release \ -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ -derivedDataPath build/DerivedData \ + -resultBundlePath "$RUNNER_TEMP/ios-build.xcresult" \ CODE_SIGNING_ALLOWED=NO \ - build | xcpretty + build 2>&1 | tee "$RUNNER_TEMP/ios-build.log" | xcpretty cd .. @@ -78,6 +84,17 @@ runs: # Create a tar for better caching (go back to example directory) tar -czf ios-dev-build.tar.gz -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")" + - name: Upload iOS build diagnostics + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ios-build-diagnostics + path: | + ${{ runner.temp }}/ios-build.log + ${{ runner.temp }}/ios-build.xcresult + if-no-files-found: ignore + retention-days: 7 + - name: Upload development build if: steps.ios-dev-cache.outputs.cache-hit != 'true' uses: actions/upload-artifact@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c48dca9..a9c07e62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: Build package + run: yarn build + - name: Lint files run: yarn lint @@ -79,7 +82,9 @@ jobs: uses: ./.github/actions/check-unstaged-files build-ios-dev: - runs-on: macos-15 + runs-on: macos-26 + env: + DEVELOPER_DIR: /Applications/Xcode_26.6.app/Contents/Developer steps: - name: Checkout Repository uses: actions/checkout@v4 diff --git a/package.json b/package.json index 6b612a45..eee8f789 100644 --- a/package.json +++ b/package.json @@ -183,6 +183,7 @@ "example": "yarn workspace react-native-css-example", "lint": "eslint", "prepare": "bob build", + "prepack": "bob build", "prepublishOnly": "bob build", "release": "release-it --config ./.config/release-it.config.ts", "start": "yarn example start",