| author | |
| committer | |
| log | 053affbed2659f90cd1b1efaaa7a75865c4131c7 |
| tree | e1a151cd1682cb866c5afdb2b2615bdb48ea8292 |
| parent | 074bcd6dc897f9c28b1bbc04737daa6ee3d8e40f |
| signature | Signed by PGP key B5690EEEBB952194 |
7 files changed, 96 insertions(+), 100 deletions(-)
apps/mobile/src/components/CompactBrandTitle.tsx+4-38| ... | @@ -1,32 +1,18 @@ | ... | @@ -1,32 +1,18 @@ |
| 1 | import Constants from "expo-constants"; | 1 | import Constants from "expo-constants"; |
| 2 | import type { | 2 | import type { NativeStackNavigationOptions } from "@react-navigation/native-stack"; |
| 3 | NativeStackHeaderItem, | ||
| 4 | NativeStackNavigationOptions, | ||
| 5 | } from "@react-navigation/native-stack"; | ||
| 6 | import { Platform, View } from "react-native"; | 3 | import { Platform, View } from "react-native"; |
| 7 | 4 | ||
| 8 | import { AppText as Text } from "./AppText"; | 5 | import { AppText as Text } from "./AppText"; |
| 9 | import { T3Wordmark } from "./T3Wordmark"; | 6 | import { T3Wordmark } from "./T3Wordmark"; |
| 10 | import { IPAD_HOME_TITLE_OFFSET } from "../lib/layoutMetrics"; | 7 | import { IPAD_HOME_TITLE_OFFSET } from "../lib/layoutMetrics"; |
| 11 | import { resolveMobileStageLabel } from "../lib/mobileBranding"; | 8 | import { resolveMobileStageLabel } from "../lib/mobileBranding"; |
| 12 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../native/native-glass"; | ||
| 13 | |||
| 14 | // Native leading items inherit different UIKit margins than title views. | ||
| 15 | const IOS_NATIVE_LEADING_TITLE_OFFSET = -6; | ||
| 16 | const IPAD_NATIVE_LEADING_TITLE_OFFSET = 7; | ||
| 17 | |||
| 18 | // Branding and connection status share an identity distinct from navigation buttons. | ||
| 19 | export const BRAND_HEADER_ITEM_IDENTIFIER = "workspace-brand"; | ||
| 20 | 9 | ||
| 21 | /** | 10 | /** |
| 22 | * Horizontal correction applied to content rendered in the brand title slot, | 11 | * Horizontal correction applied to content rendered in the brand title slot, |
| 23 | * shared with the connection-status swap so both align identically. | 12 | * shared with the connection-status swap so both align identically. |
| 24 | */ | 13 | */ |
| 25 | export function brandTitleOffset(nativeLeadingItem: boolean): number { | 14 | export function brandTitleOffset(): number { |
| 26 | if (Platform.OS !== "ios") return 0; | 15 | if (Platform.OS !== "ios") return 0; |
| 27 | if (nativeLeadingItem) { | ||
| 28 | return Platform.isPad ? IPAD_NATIVE_LEADING_TITLE_OFFSET : IOS_NATIVE_LEADING_TITLE_OFFSET; | ||
| 29 | } | ||
| 30 | return Platform.isPad ? IPAD_HOME_TITLE_OFFSET : 0; | 16 | return Platform.isPad ? IPAD_HOME_TITLE_OFFSET : 0; |
| 31 | } | 17 | } |
| 32 | 18 | ||
| ... | @@ -36,11 +22,10 @@ export function brandTitleOffset(nativeLeadingItem: boolean): number { | ... | @@ -36,11 +22,10 @@ export function brandTitleOffset(nativeLeadingItem: boolean): number { |
| 36 | export function CompactBrandTitle( | 22 | export function CompactBrandTitle( |
| 37 | props: { | 23 | props: { |
| 38 | readonly allowFontScaling?: boolean; | 24 | readonly allowFontScaling?: boolean; |
| 39 | readonly nativeLeadingItem?: boolean; | ||
| 40 | } = {}, | 25 | } = {}, |
| 41 | ) { | 26 | ) { |
| 42 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); | 27 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); |
| 43 | const titleOffset = brandTitleOffset(props.nativeLeadingItem === true); | 28 | const titleOffset = brandTitleOffset(); |
| 44 | 29 | ||
| 45 | return ( | 30 | return ( |
| 46 | <View | 31 | <View |
| ... | @@ -74,32 +59,13 @@ export function renderCompactBrandTitle() { | ... | @@ -74,32 +59,13 @@ export function renderCompactBrandTitle() { |
| 74 | return <CompactBrandTitle allowFontScaling={Platform.OS === "ios"} />; | 59 | return <CompactBrandTitle allowFontScaling={Platform.OS === "ios"} />; |
| 75 | } | 60 | } |
| 76 | 61 | ||
| 77 | export function renderCompactBrandHeaderItems(): NativeStackHeaderItem[] { | ||
| 78 | return [ | ||
| 79 | { | ||
| 80 | element: <CompactBrandTitle nativeLeadingItem />, | ||
| 81 | hidesSharedBackground: true, | ||
| 82 | identifier: BRAND_HEADER_ITEM_IDENTIFIER, | ||
| 83 | type: "custom", | ||
| 84 | }, | ||
| 85 | ]; | ||
| 86 | } | ||
| 87 | |||
| 88 | export function getCompactBrandHeaderOptions( | 62 | export function getCompactBrandHeaderOptions( |
| 89 | fallbackTitleStyle?: NativeStackNavigationOptions["headerTitleStyle"], | 63 | fallbackTitleStyle?: NativeStackNavigationOptions["headerTitleStyle"], |
| 90 | ): NativeStackNavigationOptions { | 64 | ): NativeStackNavigationOptions { |
| 91 | if (Platform.OS === "ios" && NATIVE_LIQUID_GLASS_SUPPORTED) { | ||
| 92 | return { | ||
| 93 | headerTitle: "Threads", | ||
| 94 | headerTitleStyle: { color: "transparent", fontSize: 18, fontWeight: "800" }, | ||
| 95 | title: "Threads", | ||
| 96 | unstable_headerLeftItems: renderCompactBrandHeaderItems, | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | |||
| 100 | return { | 65 | return { |
| 101 | headerTitle: renderCompactBrandTitle, | 66 | headerTitle: renderCompactBrandTitle, |
| 102 | headerTitleStyle: fallbackTitleStyle, | 67 | headerTitleStyle: fallbackTitleStyle, |
| 103 | title: "Threads", | 68 | title: "Threads", |
| 69 | unstable_headerLeftItems: undefined, | ||
| 104 | }; | 70 | }; |
| 105 | } | 71 | } |
apps/mobile/src/features/home/HomeRouteScreen.tsx+4-1| ... | @@ -2,7 +2,7 @@ import * as Arr from "effect/Array"; | ... | @@ -2,7 +2,7 @@ import * as Arr from "effect/Array"; |
| 2 | import * as Order from "effect/Order"; | 2 | import * as Order from "effect/Order"; |
| 3 | import { useNavigation } from "@react-navigation/native"; | 3 | import { useNavigation } from "@react-navigation/native"; |
| 4 | import { useEffect, useMemo, useState } from "react"; | 4 | import { useEffect, useMemo, useState } from "react"; |
| 5 | import { Platform } from "react-native"; | 5 | import { Platform, useWindowDimensions } from "react-native"; |
| 6 | 6 | ||
| 7 | import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader"; | 7 | import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader"; |
| 8 | import { useProjects, useThreadShells } from "../../state/entities"; | 8 | import { useProjects, useThreadShells } from "../../state/entities"; |
| ... | @@ -25,6 +25,7 @@ import { getConnectionAwareBrandHeaderOptions } from "./WorkspaceConnectionTitle | ... | @@ -25,6 +25,7 @@ import { getConnectionAwareBrandHeaderOptions } from "./WorkspaceConnectionTitle |
| 25 | /* ─── Route screen ───────────────────────────────────────────────────── */ | 25 | /* ─── Route screen ───────────────────────────────────────────────────── */ |
| 26 | 26 | ||
| 27 | export function HomeRouteScreen() { | 27 | export function HomeRouteScreen() { |
| 28 | const { width: windowWidth } = useWindowDimensions(); | ||
| 28 | const { layout } = useAdaptiveWorkspaceLayout(); | 29 | const { layout } = useAdaptiveWorkspaceLayout(); |
| 29 | const projects = useProjects(); | 30 | const projects = useProjects(); |
| 30 | const threads = useThreadShells(); | 31 | const threads = useThreadShells(); |
| ... | @@ -138,8 +139,10 @@ export function HomeRouteScreen() { | ... | @@ -138,8 +139,10 @@ export function HomeRouteScreen() { |
| 138 | shallow-merged. The brand slot also doubles as the connection | 139 | shallow-merged. The brand slot also doubles as the connection |
| 139 | status surface while an environment reconnects. */} | 140 | status surface while an environment reconnects. */} |
| 140 | <NativeStackScreenOptions | 141 | <NativeStackScreenOptions |
| 142 | optionsVersion={windowWidth} | ||
| 141 | options={{ | 143 | options={{ |
| 142 | ...getConnectionAwareBrandHeaderOptions({ | 144 | ...getConnectionAwareBrandHeaderOptions({ |
| 145 | headerWidth: windowWidth, | ||
| 143 | onOpenEnvironments: () => | 146 | onOpenEnvironments: () => |
| 144 | navigation.navigate("SettingsSheet", { | 147 | navigation.navigate("SettingsSheet", { |
| 145 | screen: "SettingsContent", | 148 | screen: "SettingsContent", |
apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx+21-35| ... | @@ -1,18 +1,14 @@ | ... | @@ -1,18 +1,14 @@ |
| 1 | import type { | 1 | import type { NativeStackNavigationOptions } from "@react-navigation/native-stack"; |
| 2 | NativeStackHeaderItem, | ||
| 3 | NativeStackNavigationOptions, | ||
| 4 | } from "@react-navigation/native-stack"; | ||
| 5 | import { useEffect, useRef, useState, type ReactNode } from "react"; | 2 | import { useEffect, useRef, useState, type ReactNode } from "react"; |
| 6 | import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-native"; | 3 | import { ActivityIndicator, Animated, Pressable, View } from "react-native"; |
| 7 | 4 | ||
| 8 | import { SymbolView } from "../../components/AppSymbol"; | 5 | import { SymbolView } from "../../components/AppSymbol"; |
| 9 | import { AppText as Text } from "../../components/AppText"; | 6 | import { AppText as Text } from "../../components/AppText"; |
| 10 | import { | 7 | import { |
| 11 | BRAND_HEADER_ITEM_IDENTIFIER, | ||
| 12 | brandTitleOffset, | 8 | brandTitleOffset, |
| 13 | CompactBrandTitle, | 9 | CompactBrandTitle, |
| 10 | getCompactBrandHeaderOptions, | ||
| 14 | } from "../../components/CompactBrandTitle"; | 11 | } from "../../components/CompactBrandTitle"; |
| 15 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; | ||
| 16 | import { useWorkspaceState } from "../../state/workspace"; | 12 | import { useWorkspaceState } from "../../state/workspace"; |
| 17 | import { | 13 | import { |
| 18 | workspaceConnectionStatusPresentation, | 14 | workspaceConnectionStatusPresentation, |
| ... | @@ -55,7 +51,11 @@ function useDelayedConnectionStatus(): WorkspaceConnectionStatusPresentation | n | ... | @@ -55,7 +51,11 @@ function useDelayedConnectionStatus(): WorkspaceConnectionStatusPresentation | n |
| 55 | * native-driver animated nodes blank the re-hosted view entirely. The JS driver | 51 | * native-driver animated nodes blank the re-hosted view entirely. The JS driver |
| 56 | * updates opacity through the ordinary style path, which those subviews handle. | 52 | * updates opacity through the ordinary style path, which those subviews handle. |
| 57 | */ | 53 | */ |
| 58 | function StatusFadeIn(props: { readonly children: ReactNode; readonly grow?: boolean }) { | 54 | function StatusFadeIn(props: { |
| 55 | readonly children: ReactNode; | ||
| 56 | readonly grow?: boolean; | ||
| 57 | readonly maxWidth?: number; | ||
| 58 | }) { | ||
| 59 | const opacity = useRef(new Animated.Value(0)).current; | 59 | const opacity = useRef(new Animated.Value(0)).current; |
| 60 | 60 | ||
| 61 | useEffect(() => { | 61 | useEffect(() => { |
| ... | @@ -71,7 +71,7 @@ function StatusFadeIn(props: { readonly children: ReactNode; readonly grow?: boo | ... | @@ -71,7 +71,7 @@ function StatusFadeIn(props: { readonly children: ReactNode; readonly grow?: boo |
| 71 | return ( | 71 | return ( |
| 72 | <Animated.View | 72 | <Animated.View |
| 73 | style={[ | 73 | style={[ |
| 74 | { alignItems: "center", flexDirection: "row", opacity }, | 74 | { alignItems: "center", flexDirection: "row", maxWidth: props.maxWidth, opacity }, |
| 75 | props.grow ? { flex: 1, minWidth: 0 } : null, | 75 | props.grow ? { flex: 1, minWidth: 0 } : null, |
| 76 | ]} | 76 | ]} |
| 77 | > | 77 | > |
| ... | @@ -100,6 +100,8 @@ export function WorkspaceConnectionTitle(props: { | ... | @@ -100,6 +100,8 @@ export function WorkspaceConnectionTitle(props: { |
| 100 | readonly size?: "navbar" | "pageTitle"; | 100 | readonly size?: "navbar" | "pageTitle"; |
| 101 | /** Horizontal correction so the status aligns with the brand in native title slots. */ | 101 | /** Horizontal correction so the status aligns with the brand in native title slots. */ |
| 102 | readonly statusOffset?: number; | 102 | readonly statusOffset?: number; |
| 103 | /** Space available beside the native header actions. */ | ||
| 104 | readonly maxWidth?: number; | ||
| 103 | }) { | 105 | }) { |
| 104 | const status = useDelayedConnectionStatus(); | 106 | const status = useDelayedConnectionStatus(); |
| 105 | const size = props.size ?? "navbar"; | 107 | const size = props.size ?? "navbar"; |
| ... | @@ -115,7 +117,7 @@ export function WorkspaceConnectionTitle(props: { | ... | @@ -115,7 +117,7 @@ export function WorkspaceConnectionTitle(props: { |
| 115 | } | 117 | } |
| 116 | 118 | ||
| 117 | return ( | 119 | return ( |
| 118 | <StatusFadeIn grow={props.grow}> | 120 | <StatusFadeIn grow={props.grow} maxWidth={props.maxWidth}> |
| 119 | <Pressable | 121 | <Pressable |
| 120 | accessibilityHint="Opens environment settings" | 122 | accessibilityHint="Opens environment settings" |
| 121 | accessibilityLabel={status.label} | 123 | accessibilityLabel={status.label} |
| ... | @@ -124,7 +126,7 @@ export function WorkspaceConnectionTitle(props: { | ... | @@ -124,7 +126,7 @@ export function WorkspaceConnectionTitle(props: { |
| 124 | hitSlop={8} | 126 | hitSlop={8} |
| 125 | onPress={props.onPress} | 127 | onPress={props.onPress} |
| 126 | className="flex-row items-center gap-2" | 128 | className="flex-row items-center gap-2" |
| 127 | style={{ marginLeft: props.statusOffset ?? 0 }} | 129 | style={{ flexShrink: 1, marginLeft: props.statusOffset ?? 0 }} |
| 128 | > | 130 | > |
| 129 | {status.showsProgress ? ( | 131 | {status.showsProgress ? ( |
| 130 | <ActivityIndicator colorClassName={"accent-icon-muted"} size="small" /> | 132 | <ActivityIndicator colorClassName={"accent-icon-muted"} size="small" /> |
| ... | @@ -158,40 +160,24 @@ export function WorkspaceConnectionTitle(props: { | ... | @@ -158,40 +160,24 @@ export function WorkspaceConnectionTitle(props: { |
| 158 | * this over the static brand options at mount. | 160 | * this over the static brand options at mount. |
| 159 | */ | 161 | */ |
| 160 | export function getConnectionAwareBrandHeaderOptions(opts: { | 162 | export function getConnectionAwareBrandHeaderOptions(opts: { |
| 163 | readonly headerWidth: number; | ||
| 164 | readonly trailingItemCount?: number; | ||
| 161 | readonly onOpenEnvironments: () => void; | 165 | readonly onOpenEnvironments: () => void; |
| 162 | readonly fallbackTitleStyle?: NativeStackNavigationOptions["headerTitleStyle"]; | 166 | readonly fallbackTitleStyle?: NativeStackNavigationOptions["headerTitleStyle"]; |
| 163 | }): NativeStackNavigationOptions { | 167 | }): NativeStackNavigationOptions { |
| 164 | if (Platform.OS === "ios" && NATIVE_LIQUID_GLASS_SUPPORTED) { | 168 | // Leave room for bar margins, title spacing and the 44-point native actions. |
| 165 | return { | 169 | // Long status labels must not push Settings into UIKit's overflow menu. |
| 166 | headerTitle: "Threads", | 170 | const maxWidth = Math.max(0, opts.headerWidth - 64 - 44 * (opts.trailingItemCount ?? 1)); |
| 167 | headerTitleStyle: { color: "transparent", fontSize: 18, fontWeight: "800" }, | ||
| 168 | title: "Threads", | ||
| 169 | unstable_headerLeftItems: (): NativeStackHeaderItem[] => [ | ||
| 170 | { | ||
| 171 | element: ( | ||
| 172 | <WorkspaceConnectionTitle | ||
| 173 | brand={<CompactBrandTitle nativeLeadingItem />} | ||
| 174 | onPress={opts.onOpenEnvironments} | ||
| 175 | statusOffset={brandTitleOffset(true)} | ||
| 176 | /> | ||
| 177 | ), | ||
| 178 | hidesSharedBackground: true, | ||
| 179 | identifier: BRAND_HEADER_ITEM_IDENTIFIER, | ||
| 180 | type: "custom", | ||
| 181 | }, | ||
| 182 | ], | ||
| 183 | }; | ||
| 184 | } | ||
| 185 | 171 | ||
| 186 | return { | 172 | return { |
| 173 | ...getCompactBrandHeaderOptions(opts.fallbackTitleStyle), | ||
| 187 | headerTitle: () => ( | 174 | headerTitle: () => ( |
| 188 | <WorkspaceConnectionTitle | 175 | <WorkspaceConnectionTitle |
| 189 | brand={<CompactBrandTitle />} | 176 | brand={<CompactBrandTitle />} |
| 177 | maxWidth={maxWidth} | ||
| 190 | onPress={opts.onOpenEnvironments} | 178 | onPress={opts.onOpenEnvironments} |
| 191 | statusOffset={brandTitleOffset(false)} | 179 | statusOffset={brandTitleOffset()} |
| 192 | /> | 180 | /> |
| 193 | ), | 181 | ), |
| 194 | headerTitleStyle: opts.fallbackTitleStyle, | ||
| 195 | title: "Threads", | ||
| 196 | }; | 182 | }; |
| 197 | } | 183 | } |
apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx+3-1| ... | @@ -1158,12 +1158,14 @@ function ThreadNavigationSidebarPane( | ... | @@ -1158,12 +1158,14 @@ function ThreadNavigationSidebarPane( |
| 1158 | return ( | 1158 | return ( |
| 1159 | <> | 1159 | <> |
| 1160 | <NativeStackScreenOptions | 1160 | <NativeStackScreenOptions |
| 1161 | optionsVersion={nativeHeaderItems} | 1161 | optionsVersion={[nativeHeaderItems, props.width]} |
| 1162 | options={{ | 1162 | options={{ |
| 1163 | // Re-applies the shell's static brand slot with the | 1163 | // Re-applies the shell's static brand slot with the |
| 1164 | // connection-status swap so reconnects surface in the header | 1164 | // connection-status swap so reconnects surface in the header |
| 1165 | // instead of shifting the list. | 1165 | // instead of shifting the list. |
| 1166 | ...getConnectionAwareBrandHeaderOptions({ | 1166 | ...getConnectionAwareBrandHeaderOptions({ |
| 1167 | headerWidth: props.width, | ||
| 1168 | trailingItemCount: nativeHeaderItems.length, | ||
| 1167 | onOpenEnvironments: props.onOpenEnvironmentSettings, | 1169 | onOpenEnvironments: props.onOpenEnvironmentSettings, |
| 1168 | fallbackTitleStyle: { fontSize: 18, fontWeight: "800" }, | 1170 | fallbackTitleStyle: { fontSize: 18, fontWeight: "800" }, |
| 1169 | }), | 1171 | }), |
docs/internals/mobile-navigation.md+24-12| ... | @@ -5,23 +5,30 @@ The iOS Home and thread routes share the root native stack in | ... | @@ -5,23 +5,30 @@ The iOS Home and thread routes share the root native stack in |
| 5 | controller lets UIKit animate the header between routes. The iPad sidebar owns | 5 | controller lets UIKit animate the header between routes. The iPad sidebar owns |
| 6 | a separate, single-screen stack; Android uses its own in-flow headers. | 6 | a separate, single-screen stack; Android uses its own in-flow headers. |
| 7 | 7 | ||
| 8 | On Liquid Glass iOS, Home keeps a transparent native title and renders its brand | 8 | Home and the iPad sidebar render their brand and connection status through |
| 9 | as a custom leading item. A custom `headerTitle` previously shortened the scroll | 9 | `headerTitle`, with `Threads` retained as the route title. The editor-style native |
| 10 | fade and removed the blur behind the logo. Preserve the native title when | 10 | bar aligns that title on the leading side. Do not model the brand as a toolbar |
| 11 | changing the brand or its connection-status replacement. | 11 | button: on iOS 26.5 UIKit morphs a background-free custom leading item's rectangle |
| 12 | 12 | into the next screen's glass back button, even with distinct item identifiers. | |
| 13 | Both brand states use `BRAND_HEADER_ITEM_IDENTIFIER` from | 13 | Using the title slot lets the brand and native back button animate separately. |
| 14 | [`CompactBrandTitle.tsx`](../../apps/mobile/src/components/CompactBrandTitle.tsx). | 14 | |
| 15 | The native-stack and react-native-screens patches forward custom item identifiers | 15 | The connection-status title has a maximum width based on its header's width and |
| 16 | to `UIBarButtonItem.identifier`, just as they do for native buttons and menus. | 16 | trailing actions. A long environment name or larger text must not push Settings |
| 17 | UIKit otherwise matches transition items using their position and content, so | 17 | into UIKit's overflow menu. Keep the full status as the accessibility label |
| 18 | the brand needs an explicit identity separate from navigation controls. Connection | 18 | while visually truncating it. The iPad sidebar uses its pane width, not the full |
| 19 | status changes retain that identity. See [Apple's identifier documentation](https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier). | 19 | window width. |
| 20 | |||
| 21 | On iOS 26, UIKit does not recognize Fabric's custom text views when shaping the | ||
| 22 | native scroll-edge fade. The screens patch gives custom title subviews an empty, | ||
| 23 | non-interactive native label matching their bounds. This supplies the fade's | ||
| 24 | geometry without drawing anything or turning the title back into a toolbar item. | ||
| 25 | Check the top scroll fade as well as push/pop when changing these title views. | ||
| 20 | 26 | ||
| 21 | The react-native-screens patch caches leading, trailing, and center item groups | 27 | The react-native-screens patch caches leading, trailing, and center item groups |
| 22 | independently. A button can belong to only one group: constructing another group | 28 | independently. A button can belong to only one group: constructing another group |
| 23 | with the same button removes it from the previous one. Unrelated menu updates | 29 | with the same button removes it from the previous one. Unrelated menu updates |
| 24 | must therefore preserve the other groups while UIKit may be animating them. | 30 | must therefore preserve the other groups while UIKit may be animating them. |
| 31 | |||
| 25 | Each cache includes the owning header config, its item values, and custom native | 32 | Each cache includes the owning header config, its item values, and custom native |
| 26 | item identities, so changed content or remounted event emitters still rebuild. | 33 | item identities, so changed content or remounted event emitters still rebuild. |
| 27 | 34 | ||
| ... | @@ -29,3 +36,8 @@ Custom header identifiers require native code generation and a new mobile build; | ... | @@ -29,3 +36,8 @@ Custom header identifiers require native code generation and a new mobile build; |
| 29 | an over-the-air JavaScript update alone is insufficient. The Android view manager | 36 | an over-the-air JavaScript update alone is insufficient. The Android view manager |
| 30 | implements the generated identifier setter as a no-op because this behavior is | 37 | implements the generated identifier setter as a no-op because this behavior is |
| 31 | specific to iOS 26 and later. | 38 | specific to iOS 26 and later. |
| 39 | |||
| 40 | After changing a dependency patch, refresh CocoaPods before rebuilding an | ||
| 41 | existing iOS project. pnpm installs each patch hash in a different directory; | ||
| 42 | an old Pods project can keep compiling the previous directory even though Metro | ||
| 43 | and `apps/mobile/node_modules` resolve to the new patch. |
patches/react-native-screens@4.26.2.patch+34-7| ... | @@ -1107,18 +1107,45 @@ index 1c844846a5c66e31cfa530ca462774d2fb6bbea1..a60b96b44e85d3f4b29dc0d190b95ab6 | ... | @@ -1107,18 +1107,45 @@ index 1c844846a5c66e31cfa530ca462774d2fb6bbea1..a60b96b44e85d3f4b29dc0d190b95ab6 |
| 1107 | 1107 | ||
| 1108 | if (needsNavigationControllerLayout) { | 1108 | if (needsNavigationControllerLayout) { |
| 1109 | diff --git a/ios/RNSScreenStackHeaderSubview.mm b/ios/RNSScreenStackHeaderSubview.mm | 1109 | diff --git a/ios/RNSScreenStackHeaderSubview.mm b/ios/RNSScreenStackHeaderSubview.mm |
| 1110 | index add33c4807e038dcd846f6c72b2fc472ded02809..9a7c7a8a775579c85de39f921e7a362241a72075 100644 | 1110 | index add33c4807e038dcd846f6c72b2fc472ded02809..489afa5fa2da09c1ec6986d1832b58bb595396ae 100644 |
| 1111 | --- a/ios/RNSScreenStackHeaderSubview.mm | 1111 | --- a/ios/RNSScreenStackHeaderSubview.mm |
| 1112 | +++ b/ios/RNSScreenStackHeaderSubview.mm | 1112 | +++ b/ios/RNSScreenStackHeaderSubview.mm |
| 1113 | @@ -20,6 +20,7 @@ @implementation RNSScreenStackHeaderSubview { | 1113 | @@ -20,7 +20,9 @@ @implementation RNSScreenStackHeaderSubview { |
| 1114 | // This is a strong reference to UIBarButtonItem which creates a retain cycle. | 1114 | // This is a strong reference to UIBarButtonItem which creates a retain cycle. |
| 1115 | // The cycle is cleared via `invalidateUIBarButtonItem` method, called by `invalidate` callback. | 1115 | // The cycle is cleared via `invalidateUIBarButtonItem` method, called by `invalidate` callback. |
| 1116 | UIBarButtonItem *_barButtonItem; | 1116 | UIBarButtonItem *_barButtonItem; |
| 1117 | + NSString *_barButtonItemIdentifier; | 1117 | + NSString *_barButtonItemIdentifier; |
| 1118 | BOOL _hidesSharedBackground; | 1118 | BOOL _hidesSharedBackground; |
| 1119 | + UILabel *_titleScrollEdgeEffectGuide; | ||
| 1119 | } | 1120 | } |
| 1120 | 1121 | ||
| 1121 | @@ -124,6 +125,7 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props:: | 1122 | #pragma mark - Common |
| 1123 | @@ -102,5 +104,24 @@ - (void)updateShadowStateWithFrame:(CGRect)frame | ||
| 1124 | - (void)layoutSubviews | ||
| 1125 | { | ||
| 1126 | [super layoutSubviews]; | ||
| 1127 | +#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) | ||
| 1128 | + if (@available(iOS 26.0, *)) { | ||
| 1129 | + if (_type == RNSScreenStackHeaderSubviewTypeTitle || _type == RNSScreenStackHeaderSubviewTypeCenter) { | ||
| 1130 | + // UIKit shapes the navigation bar's scroll-edge fade around native labels, | ||
| 1131 | + // but does not recognize Fabric's custom text views. An empty, non-interactive | ||
| 1132 | + // label supplies the title's geometry without drawing or becoming a bar item. | ||
| 1133 | + if (_titleScrollEdgeEffectGuide == nil) { | ||
| 1134 | + _titleScrollEdgeEffectGuide = [UILabel new]; | ||
| 1135 | + _titleScrollEdgeEffectGuide.accessibilityElementsHidden = YES; | ||
| 1136 | + // Append after Fabric's children so their mount/unmount indices stay intact. | ||
| 1137 | + [self addSubview:_titleScrollEdgeEffectGuide]; | ||
| 1138 | + } | ||
| 1139 | + _titleScrollEdgeEffectGuide.frame = self.bounds; | ||
| 1140 | + } else { | ||
| 1141 | + [_titleScrollEdgeEffectGuide removeFromSuperview]; | ||
| 1142 | + _titleScrollEdgeEffectGuide = nil; | ||
| 1143 | + } | ||
| 1144 | + } | ||
| 1145 | +#endif | ||
| 1146 | [self updateShadowStateInContextOfAncestorView:[self findNavigationBar]]; | ||
| 1147 | } | ||
| 1148 | @@ -124,6 +145,7 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props:: | ||
| 1122 | const auto &newHeaderSubviewProps = *std::static_pointer_cast<const react::RNSScreenStackHeaderSubviewProps>(props); | 1149 | const auto &newHeaderSubviewProps = *std::static_pointer_cast<const react::RNSScreenStackHeaderSubviewProps>(props); |
| 1123 | 1150 | ||
| 1124 | [self setType:[RNSConvert RNSScreenStackHeaderSubviewTypeFromCppEquivalent:newHeaderSubviewProps.type]]; | 1151 | [self setType:[RNSConvert RNSScreenStackHeaderSubviewTypeFromCppEquivalent:newHeaderSubviewProps.type]]; |
| ... | @@ -1126,7 +1153,7 @@ index add33c4807e038dcd846f6c72b2fc472ded02809..9a7c7a8a775579c85de39f921e7a3622 | ... | @@ -1126,7 +1153,7 @@ index add33c4807e038dcd846f6c72b2fc472ded02809..9a7c7a8a775579c85de39f921e7a3622 |
| 1126 | [self setHidesSharedBackground:newHeaderSubviewProps.hidesSharedBackground]; | 1153 | [self setHidesSharedBackground:newHeaderSubviewProps.hidesSharedBackground]; |
| 1127 | [self setSynchronousShadowStateUpdatesEnabled:newHeaderSubviewProps.synchronousShadowStateUpdatesEnabled]; | 1154 | [self setSynchronousShadowStateUpdatesEnabled:newHeaderSubviewProps.synchronousShadowStateUpdatesEnabled]; |
| 1128 | [super updateProps:props oldProps:oldProps]; | 1155 | [super updateProps:props oldProps:oldProps]; |
| 1129 | @@ -276,7 +278,14 @@ - (void)configureBarButtonItem | 1156 | @@ -276,7 +298,14 @@ - (void)configureBarButtonItem |
| 1130 | #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) | 1157 | #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) |
| 1131 | if (@available(iOS 26.0, *)) { | 1158 | if (@available(iOS 26.0, *)) { |
| 1132 | if (_barButtonItem != nil) { | 1159 | if (_barButtonItem != nil) { |
| ... | @@ -1142,7 +1169,7 @@ index add33c4807e038dcd846f6c72b2fc472ded02809..9a7c7a8a775579c85de39f921e7a3622 | ... | @@ -1142,7 +1169,7 @@ index add33c4807e038dcd846f6c72b2fc472ded02809..9a7c7a8a775579c85de39f921e7a3622 |
| 1142 | } | 1169 | } |
| 1143 | } | 1170 | } |
| 1144 | #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) | 1171 | #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) |
| 1145 | @@ -284,10 +293,22 @@ - (void)configureBarButtonItem | 1172 | @@ -284,10 +313,22 @@ - (void)configureBarButtonItem |
| 1146 | 1173 | ||
| 1147 | - (void)setHidesSharedBackground:(BOOL)hidesSharedBackground | 1174 | - (void)setHidesSharedBackground:(BOOL)hidesSharedBackground |
| 1148 | { | 1175 | { |
| ... | @@ -1166,7 +1193,7 @@ index add33c4807e038dcd846f6c72b2fc472ded02809..9a7c7a8a775579c85de39f921e7a3622 | ... | @@ -1166,7 +1193,7 @@ index add33c4807e038dcd846f6c72b2fc472ded02809..9a7c7a8a775579c85de39f921e7a3622 |
| 1166 | 1193 | ||
| 1167 | // Needed because of this: https://github.com/facebook/react-native/pull/37274 | 1194 | // Needed because of this: https://github.com/facebook/react-native/pull/37274 |
| 1168 | diff --git a/lib/commonjs/components/ScreenStackHeaderConfig.js b/lib/commonjs/components/ScreenStackHeaderConfig.js | 1195 | diff --git a/lib/commonjs/components/ScreenStackHeaderConfig.js b/lib/commonjs/components/ScreenStackHeaderConfig.js |
| 1169 | index 8c509f2657105cfd86448347c1a01fb73651ff9e..5e3b5185c0ea33c6b98f5daed69239d2c0d0ce90 100644 | 1196 | index 8c509f2657105cfd86448347c1a01fb73651ff9e..e668e089a1788973cb122a150be7846364a2a2cf 100644 |
| 1170 | --- a/lib/commonjs/components/ScreenStackHeaderConfig.js | 1197 | --- a/lib/commonjs/components/ScreenStackHeaderConfig.js |
| 1171 | +++ b/lib/commonjs/components/ScreenStackHeaderConfig.js | 1198 | +++ b/lib/commonjs/components/ScreenStackHeaderConfig.js |
| 1172 | @@ -26,17 +26,42 @@ const ScreenStackHeaderConfig = exports.ScreenStackHeaderConfig = /*#__PURE__*/_ | 1199 | @@ -26,17 +26,42 @@ const ScreenStackHeaderConfig = exports.ScreenStackHeaderConfig = /*#__PURE__*/_ |
| ... | @@ -1338,7 +1365,7 @@ index ab93f62e4a7049d63c6681cecbd6cf8b1d07ce10..9dca2ab640e92161eb647b819a989acd | ... | @@ -1338,7 +1365,7 @@ index ab93f62e4a7049d63c6681cecbd6cf8b1d07ce10..9dca2ab640e92161eb647b819a989acd |
| 1338 | //# sourceMappingURL=prepareHeaderBarButtonItems.js.map | 1365 | //# sourceMappingURL=prepareHeaderBarButtonItems.js.map |
| 1339 | \ No newline at end of file | 1366 | \ No newline at end of file |
| 1340 | diff --git a/lib/module/components/ScreenStackHeaderConfig.js b/lib/module/components/ScreenStackHeaderConfig.js | 1367 | diff --git a/lib/module/components/ScreenStackHeaderConfig.js b/lib/module/components/ScreenStackHeaderConfig.js |
| 1341 | index 68e3ba6a78314aba908d07098bb6982d11e6ee80..b787b3cf0284ffb81c80360a06c4059277bf4c1e 100644 | 1368 | index 68e3ba6a78314aba908d07098bb6982d11e6ee80..db5d17bfdac19ed3e664b55b6a73ce49766ac7c5 100644 |
| 1342 | --- a/lib/module/components/ScreenStackHeaderConfig.js | 1369 | --- a/lib/module/components/ScreenStackHeaderConfig.js |
| 1343 | +++ b/lib/module/components/ScreenStackHeaderConfig.js | 1370 | +++ b/lib/module/components/ScreenStackHeaderConfig.js |
| 1344 | @@ -22,17 +22,42 @@ export const ScreenStackHeaderConfig = /*#__PURE__*/React.forwardRef((props, ref | 1371 | @@ -22,17 +22,42 @@ export const ScreenStackHeaderConfig = /*#__PURE__*/React.forwardRef((props, ref |
pnpm-lock.yaml+6-6| ... | @@ -94,7 +94,7 @@ patchedDependencies: | ... | @@ -94,7 +94,7 @@ patchedDependencies: |
| 94 | react-native-gesture-handler@2.32.0: 808eb26f9e57cf4945efd3985af4d9c764da6f91f4c9764433cc868602bbf4d3 | 94 | react-native-gesture-handler@2.32.0: 808eb26f9e57cf4945efd3985af4d9c764da6f91f4c9764433cc868602bbf4d3 |
| 95 | react-native-keyboard-controller@1.21.13: 20be72c84d74253acdcfefbc6defe36dc396944f1a44cab2bdd0e3cd572ae008 | 95 | react-native-keyboard-controller@1.21.13: 20be72c84d74253acdcfefbc6defe36dc396944f1a44cab2bdd0e3cd572ae008 |
| 96 | react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675 | 96 | react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675 |
| 97 | react-native-screens@4.26.2: 5d2e7beefe55f0b7ee157993cbc0ec38d32aa558e4e780764381fe8f96ba728d | 97 | react-native-screens@4.26.2: 149bef30a66351ea9b26b42f87b78c539cb52b880f2387bb323ec80dcac84006 |
| 98 | 98 | ||
| 99 | importers: | 99 | importers: |
| 100 | 100 | ||
| ... | @@ -248,7 +248,7 @@ importers: | ... | @@ -248,7 +248,7 @@ importers: |
| 248 | version: 7.3.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 248 | version: 7.3.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| 249 | '@react-navigation/native-stack': | 249 | '@react-navigation/native-stack': |
| 250 | specifier: 7.17.6 | 250 | specifier: 7.17.6 |
| 251 | version: 7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(1d8c6f507069f6f6aada9b6419c84a6c) | 251 | version: 7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(d307537762dff86bcf277a4ec64a11d8) |
| 252 | '@shikijs/core': | 252 | '@shikijs/core': |
| 253 | specifier: 4.2.0 | 253 | specifier: 4.2.0 |
| 254 | version: 4.2.0 | 254 | version: 4.2.0 |
| ... | @@ -416,7 +416,7 @@ importers: | ... | @@ -416,7 +416,7 @@ importers: |
| 416 | version: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 416 | version: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| 417 | react-native-screens: | 417 | react-native-screens: |
| 418 | specifier: ~4.26.0 | 418 | specifier: ~4.26.0 |
| 419 | version: 4.26.2(patch_hash=5d2e7beefe55f0b7ee157993cbc0ec38d32aa558e4e780764381fe8f96ba728d)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 419 | version: 4.26.2(patch_hash=149bef30a66351ea9b26b42f87b78c539cb52b880f2387bb323ec80dcac84006)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| 420 | react-native-shiki-engine: | 420 | react-native-shiki-engine: |
| 421 | specifier: ^0.3.12 | 421 | specifier: ^0.3.12 |
| 422 | version: 0.3.12(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 422 | version: 0.3.12(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| ... | @@ -13880,7 +13880,7 @@ snapshots: | ... | @@ -13880,7 +13880,7 @@ snapshots: |
| 13880 | optionalDependencies: | 13880 | optionalDependencies: |
| 13881 | '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 13881 | '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| 13882 | 13882 | ||
| 13883 | '@react-navigation/native-stack@7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(1d8c6f507069f6f6aada9b6419c84a6c)': | 13883 | '@react-navigation/native-stack@7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(d307537762dff86bcf277a4ec64a11d8)': |
| 13884 | dependencies: | 13884 | dependencies: |
| 13885 | '@react-navigation/elements': 2.9.26(c10301b6e0c42fc6434d2b643197a81e) | 13885 | '@react-navigation/elements': 2.9.26(c10301b6e0c42fc6434d2b643197a81e) |
| 13886 | '@react-navigation/native': 7.3.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 13886 | '@react-navigation/native': 7.3.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| ... | @@ -13888,7 +13888,7 @@ snapshots: | ... | @@ -13888,7 +13888,7 @@ snapshots: |
| 13888 | react: 19.2.3 | 13888 | react: 19.2.3 |
| 13889 | react-native: 0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | 13889 | react-native: 0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) |
| 13890 | react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 13890 | react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| 13891 | react-native-screens: 4.26.2(patch_hash=5d2e7beefe55f0b7ee157993cbc0ec38d32aa558e4e780764381fe8f96ba728d)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | 13891 | react-native-screens: 4.26.2(patch_hash=149bef30a66351ea9b26b42f87b78c539cb52b880f2387bb323ec80dcac84006)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) |
| 13892 | sf-symbols-typescript: 2.2.0 | 13892 | sf-symbols-typescript: 2.2.0 |
| 13893 | warn-once: 0.1.1 | 13893 | warn-once: 0.1.1 |
| 13894 | transitivePeerDependencies: | 13894 | transitivePeerDependencies: |
| ... | @@ -19492,7 +19492,7 @@ snapshots: | ... | @@ -19492,7 +19492,7 @@ snapshots: |
| 19492 | react: 19.2.3 | 19492 | react: 19.2.3 |
| 19493 | react-native: 0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | 19493 | react-native: 0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) |
| 19494 | 19494 | ||
| 19495 | react-native-screens@4.26.2(patch_hash=5d2e7beefe55f0b7ee157993cbc0ec38d32aa558e4e780764381fe8f96ba728d)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3): | 19495 | react-native-screens@4.26.2(patch_hash=149bef30a66351ea9b26b42f87b78c539cb52b880f2387bb323ec80dcac84006)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3): |
| 19496 | dependencies: | 19496 | dependencies: |
| 19497 | react: 19.2.3 | 19497 | react: 19.2.3 |
| 19498 | react-freeze: 1.0.4(react@19.2.3) | 19498 | react-freeze: 1.0.4(react@19.2.3) |