authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-08-28 18:41:42-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-28 18:41:42-07:00
log4669eab8e0e01d07141a2cb49ca21a9e15ae1429
tree5e8093096bdac110520e09ee0dd2f8ed8872692e
parent702a6ade3c69bf1c9ce06ad0451dbd8e0ecc7be9
signature Signed by PGP key B5690EEEBB952194

fix(mobile): stabilize iOS header item transitions (#8607)

Co-authored-by: Julius Marminge <julius@mac.lan>

7 files changed, 363 insertions(+), 130 deletions(-)

apps/mobile/src/components/CompactBrandTitle.tsx+4
...@@ -15,6 +15,9 @@ import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../native/native-glass";...@@ -15,6 +15,9 @@ import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../native/native-glass";
15const IOS_NATIVE_LEADING_TITLE_OFFSET = -6;15const IOS_NATIVE_LEADING_TITLE_OFFSET = -6;
16const IPAD_NATIVE_LEADING_TITLE_OFFSET = 7;16const IPAD_NATIVE_LEADING_TITLE_OFFSET = 7;
1717
18// Branding and connection status share an identity distinct from navigation buttons.
19export const BRAND_HEADER_ITEM_IDENTIFIER = "workspace-brand";
20
18/**21/**
19 * Horizontal correction applied to content rendered in the brand title slot,22 * Horizontal correction applied to content rendered in the brand title slot,
20 * shared with the connection-status swap so both align identically.23 * shared with the connection-status swap so both align identically.
...@@ -76,6 +79,7 @@ export function renderCompactBrandHeaderItems(): NativeStackHeaderItem[] {...@@ -76,6 +79,7 @@ export function renderCompactBrandHeaderItems(): NativeStackHeaderItem[] {
76 {79 {
77 element: <CompactBrandTitle nativeLeadingItem />,80 element: <CompactBrandTitle nativeLeadingItem />,
78 hidesSharedBackground: true,81 hidesSharedBackground: true,
82 identifier: BRAND_HEADER_ITEM_IDENTIFIER,
79 type: "custom",83 type: "custom",
80 },84 },
81 ];85 ];
apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx+6-1
...@@ -7,7 +7,11 @@ import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-na...@@ -7,7 +7,11 @@ import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-na
77
8import { SymbolView } from "../../components/AppSymbol";8import { SymbolView } from "../../components/AppSymbol";
9import { AppText as Text } from "../../components/AppText";9import { AppText as Text } from "../../components/AppText";
10import { brandTitleOffset, CompactBrandTitle } from "../../components/CompactBrandTitle";10import {
11 BRAND_HEADER_ITEM_IDENTIFIER,
12 brandTitleOffset,
13 CompactBrandTitle,
14} from "../../components/CompactBrandTitle";
11import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";15import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
12import { useWorkspaceState } from "../../state/workspace";16import { useWorkspaceState } from "../../state/workspace";
13import {17import {
...@@ -172,6 +176,7 @@ export function getConnectionAwareBrandHeaderOptions(opts: {...@@ -172,6 +176,7 @@ export function getConnectionAwareBrandHeaderOptions(opts: {
172 />176 />
173 ),177 ),
174 hidesSharedBackground: true,178 hidesSharedBackground: true,
179 identifier: BRAND_HEADER_ITEM_IDENTIFIER,
175 type: "custom",180 type: "custom",
176 },181 },
177 ],182 ],
docs/internals/mobile-navigation.md created+31
...@@ -0,0 +1,31 @@
1# Mobile navigation headers
2
3The iOS Home and thread routes share the root native stack in
4[`Stack.tsx`](../../apps/mobile/src/Stack.tsx). Keeping them in one navigation
5controller lets UIKit animate the header between routes. The iPad sidebar owns
6a separate, single-screen stack; Android uses its own in-flow headers.
7
8On Liquid Glass iOS, Home keeps a transparent native title and renders its brand
9as a custom leading item. A custom `headerTitle` previously shortened the scroll
10fade and removed the blur behind the logo. Preserve the native title when
11changing the brand or its connection-status replacement.
12
13Both brand states use `BRAND_HEADER_ITEM_IDENTIFIER` from
14[`CompactBrandTitle.tsx`](../../apps/mobile/src/components/CompactBrandTitle.tsx).
15The native-stack and react-native-screens patches forward custom item identifiers
16to `UIBarButtonItem.identifier`, just as they do for native buttons and menus.
17UIKit otherwise matches transition items using their position and content, so
18the brand needs an explicit identity separate from navigation controls. Connection
19status changes retain that identity. See [Apple's identifier documentation](https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier).
20
21The react-native-screens patch caches leading, trailing, and center item groups
22independently. A button can belong to only one group: constructing another group
23with the same button removes it from the previous one. Unrelated menu updates
24must therefore preserve the other groups while UIKit may be animating them.
25Each cache includes the owning header config, its item values, and custom native
26item identities, so changed content or remounted event emitters still rebuild.
27
28Custom header identifiers require native code generation and a new mobile build;
29an over-the-air JavaScript update alone is insufficient. The Android view manager
30implements the generated identifier setter as a no-op because this behavior is
31specific to iOS 26 and later.
docs/internals/overview.md+1
...@@ -132,6 +132,7 @@ already dispatch....@@ -132,6 +132,7 @@ already dispatch.
132## Related132## Related
133133
134- [Workspace layout](./workspace-layout.md), [Glossary](./glossary.md)134- [Workspace layout](./workspace-layout.md), [Glossary](./glossary.md)
135- [Mobile navigation headers](./mobile-navigation.md)
135- [Remote environments](./remote.md), [Server updates](./server-updates.md)136- [Remote environments](./remote.md), [Server updates](./server-updates.md)
136- [Resource telemetry](./resource-telemetry.md)137- [Resource telemetry](./resource-telemetry.md)
137- [Product analytics](./product-analytics.md)138- [Product analytics](./product-analytics.md)
patches/@react-navigation%2Fnative-stack@7.17.6.patch+103-36
...@@ -1,8 +1,30 @@...@@ -1,8 +1,30 @@
1diff --git a/lib/module/views/NativeStackView.native.js b/lib/module/views/NativeStackView.native.js
2index c342e90bffde9ad5044b6167bdde2e19be219270..5d3e4407aa1bb68b873cf164adf5a9f0b487bb0b 100644
3--- a/lib/module/views/NativeStackView.native.js
4+++ b/lib/module/views/NativeStackView.native.js
5@@ -370,6 +370,17 @@ export function NativeStackView({
6 return /*#__PURE__*/_jsx(SafeAreaProviderCompat, {
7 children: /*#__PURE__*/_jsx(ScreenStack, {
8 style: styles.container,
9+ onFinishTransitioning: () => {
10+ // Surface UIKit's transition-completion callback to every route of
11+ // this navigator. Unlike transitionEnd, this also fires when a modal
12+ // finishes dismissing — where the presenting screen below receives no
13+ // appearance callbacks — and for a gesture-driven dismissal it fires
14+ // before the state pop, while the modal route is still the focused
15+ // one, so the event must not be targeted at a single route.
16+ navigation.emit({
17+ type: 'finishTransitioning'
18+ });
19+ },
20 children: state.routes.concat(state.preloadedRoutes).map((route, index) => {
21 const descriptor = descriptors[route.key] ?? preloadedDescriptors[route.key];
22 const isFocused = state.index === index;
1diff --git a/lib/module/views/useHeaderConfigProps.js b/lib/module/views/useHeaderConfigProps.js23diff --git a/lib/module/views/useHeaderConfigProps.js b/lib/module/views/useHeaderConfigProps.js
2index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b01175ec19220 10064424index 9c21aa48cb580704aa17e59d9cdadcc612a1e4ff..a1b3a38368c5ef2257570f382e87df5d6f55b11e 100644
3--- a/lib/module/views/useHeaderConfigProps.js25--- a/lib/module/views/useHeaderConfigProps.js
4+++ b/lib/module/views/useHeaderConfigProps.js26+++ b/lib/module/views/useHeaderConfigProps.js
5@@ -19,6 +19,12 @@27@@ -19,6 +19,12 @@ const processBarButtonItems = (items, colors, fonts) => {
6 }28 }
7 return item;29 return item;
8 }30 }
...@@ -15,7 +37,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -15,7 +37,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
15 if (item.type === 'button' || item.type === 'menu') {37 if (item.type === 'button' || item.type === 'menu') {
16 if (item.type === 'menu' && item.menu == null) {38 if (item.type === 'menu' && item.menu == null) {
17 throw new Error(`Menu item must have a 'menu' property defined: ${JSON.stringify(item)}`);39 throw new Error(`Menu item must have a 'menu' property defined: ${JSON.stringify(item)}`);
18@@ -79,7 +85,7 @@40@@ -79,7 +85,7 @@ const processBarButtonItems = (items, colors, fonts) => {
19 }41 }
20 return processedItem;42 return processedItem;
21 }43 }
...@@ -24,7 +46,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -24,7 +46,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
24 }).filter(item => item != null);46 }).filter(item => item != null);
25 };47 };
26 const transformIcon = icon => {48 const transformIcon = icon => {
27@@ -158,8 +164,12 @@49@@ -158,8 +164,12 @@ export function useHeaderConfigProps({
28 headerBack,50 headerBack,
29 route,51 route,
30 title,52 title,
...@@ -38,7 +60,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -38,7 +60,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
38 }) {60 }) {
39 const {61 const {
40 direction62 direction
41@@ -255,6 +265,10 @@63@@ -255,6 +265,10 @@ export function useHeaderConfigProps({
42 tintColor,64 tintColor,
43 canGoBack65 canGoBack
44 });66 });
...@@ -49,7 +71,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -49,7 +71,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
49 let rightItems = headerRightItems?.({71 let rightItems = headerRightItems?.({
50 tintColor,72 tintColor,
51 canGoBack73 canGoBack
52@@ -264,6 +278,10 @@74@@ -264,6 +278,10 @@ export function useHeaderConfigProps({
53 // So we need to reverse them here to match the order75 // So we need to reverse them here to match the order
54 rightItems = [...rightItems].reverse();76 rightItems = [...rightItems].reverse();
55 }77 }
...@@ -60,7 +82,14 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -60,7 +82,14 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
60 const children = /*#__PURE__*/_jsxs(_Fragment, {82 const children = /*#__PURE__*/_jsxs(_Fragment, {
61 children: [Platform.OS === 'ios' ? /*#__PURE__*/_jsxs(_Fragment, {83 children: [Platform.OS === 'ios' ? /*#__PURE__*/_jsxs(_Fragment, {
62 children: [leftItems ? leftItems.map((item, index) => {84 children: [leftItems ? leftItems.map((item, index) => {
63@@ -278,7 +296,15 @@85@@ -272,13 +290,23 @@ export function useHeaderConfigProps({
86 // eslint-disable-next-line @eslint-react/no-array-index-key
87 , {
88 hidesSharedBackground: item.hidesSharedBackground,
89+ identifier: item.identifier,
90 children: item.element
91 }, index);
92 }
64 return null;93 return null;
65 }) : headerLeftElement != null ? /*#__PURE__*/_jsx(ScreenStackHeaderLeftView, {94 }) : headerLeftElement != null ? /*#__PURE__*/_jsx(ScreenStackHeaderLeftView, {
66 children: headerLeftElement95 children: headerLeftElement
...@@ -69,6 +98,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -69,6 +98,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
69+ if (item.type === 'custom') {98+ if (item.type === 'custom') {
70+ return /*#__PURE__*/_jsx(ScreenStackHeaderCenterView, {99+ return /*#__PURE__*/_jsx(ScreenStackHeaderCenterView, {
71+ hidesSharedBackground: item.hidesSharedBackground,100+ hidesSharedBackground: item.hidesSharedBackground,
101+ identifier: item.identifier,
72+ children: item.element102+ children: item.element
73+ }, index);103+ }, index);
74+ }104+ }
...@@ -77,7 +107,15 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -77,7 +107,15 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
77 children: headerTitleElement107 children: headerTitleElement
78 }) : null]108 }) : null]
79 }) : /*#__PURE__*/_jsxs(_Fragment, {109 }) : /*#__PURE__*/_jsxs(_Fragment, {
80@@ -356,6 +382,7 @@110@@ -321,6 +349,7 @@ export function useHeaderConfigProps({
111 // eslint-disable-next-line @eslint-react/no-array-index-key
112 , {
113 hidesSharedBackground: item.hidesSharedBackground,
114+ identifier: item.identifier,
115 children: item.element
116 }, index);
117 }
118@@ -356,6 +385,7 @@ export function useHeaderConfigProps({
81 largeTitleFontWeight,119 largeTitleFontWeight,
82 largeTitleHideShadow: headerLargeTitleShadowVisible === false,120 largeTitleHideShadow: headerLargeTitleShadowVisible === false,
83 title: titleText,121 title: titleText,
...@@ -85,7 +123,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -85,7 +123,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
85 titleColor,123 titleColor,
86 titleFontFamily,124 titleFontFamily,
87 titleFontSize,125 titleFontSize,
88@@ -364,9 +391,12 @@126@@ -364,6 +394,9 @@ export function useHeaderConfigProps({
89 disableTopInsetApplication: !headerTopInsetEnabled,127 disableTopInsetApplication: !headerTopInsetEnabled,
90 translucent: translucent === true,128 translucent: translucent === true,
91 children,129 children,
...@@ -95,30 +133,59 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117...@@ -95,30 +133,59 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
95 headerLeftBarButtonItems: processBarButtonItems(leftItems, colors, fonts),133 headerLeftBarButtonItems: processBarButtonItems(leftItems, colors, fonts),
96 headerRightBarButtonItems: processBarButtonItems(rightItems, colors, fonts),134 headerRightBarButtonItems: processBarButtonItems(rightItems, colors, fonts),
97 experimental_userInterfaceStyle: dark ? 'dark' : 'light'135 experimental_userInterfaceStyle: dark ? 'dark' : 'light'
98 };136diff --git a/lib/typescript/src/types.d.ts b/lib/typescript/src/types.d.ts
99 }137index 2f1351a89f0f2e67e854dc81e392198326b71834..ccf8556911fa1fa17588f06b56edb2151a712bf0 100644
100-//# sourceMappingURL=useHeaderConfigProps.js.map138--- a/lib/typescript/src/types.d.ts
101\ No newline at end of file139+++ b/lib/typescript/src/types.d.ts
102+//# sourceMappingURL=useHeaderConfigProps.js.map140@@ -1091,6 +1091,13 @@ export type NativeStackHeaderItemSpacing = {
103diff --git a/lib/module/views/NativeStackView.native.js b/lib/module/views/NativeStackView.native.js141 */
104index c342e90..5d3e440 100644142 export type NativeStackHeaderItemCustom = {
105--- a/lib/module/views/NativeStackView.native.js143 type: 'custom';
106+++ b/lib/module/views/NativeStackView.native.js144+ /**
107@@ -370,6 +370,17 @@ export function NativeStackView({145+ * An identifier used to match items across transitions.
108 return /*#__PURE__*/_jsx(SafeAreaProviderCompat, {146+ * Only available from iOS 26.0 and later.
109 children: /*#__PURE__*/_jsx(ScreenStack, {147+ *
110 style: styles.container,148+ * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier
111+ onFinishTransitioning: () => {149+ */
112+ // Surface UIKit's transition-completion callback to every route of150+ identifier?: string;
113+ // this navigator. Unlike transitionEnd, this also fires when a modal151 /**
114+ // finishes dismissing — where the presenting screen below receives no152 * A React Element to display as the item.
115+ // appearance callbacks — and for a gesture-driven dismissal it fires153 */
116+ // before the state pop, while the modal route is still the focused154diff --git a/src/types.tsx b/src/types.tsx
117+ // one, so the event must not be targeted at a single route.155index 7488b1cf20afe36b5c585fbfcd369471fdb6ab8d..f4bd78e48c687024ba84e998fac79e9eb16f1a80 100644
118+ navigation.emit({156--- a/src/types.tsx
119+ type: 'finishTransitioning'157+++ b/src/types.tsx
120+ });158@@ -1156,6 +1156,13 @@ export type NativeStackHeaderItemSpacing = {
121+ },159 */
122 children: state.routes.concat(state.preloadedRoutes).map((route, index) => {160 export type NativeStackHeaderItemCustom = {
123 const descriptor = descriptors[route.key] ?? preloadedDescriptors[route.key];161 type: 'custom';
124 const isFocused = state.index === index;162+ /**
163+ * An identifier used to match items across transitions.
164+ * Only available from iOS 26.0 and later.
165+ *
166+ * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier
167+ */
168+ identifier?: string;
169 /**
170 * A React Element to display as the item.
171 */
172diff --git a/src/views/useHeaderConfigProps.tsx b/src/views/useHeaderConfigProps.tsx
173index 3f3fff405048b3c2250afbe99dd7f35cecb9a6d5..bb062b898cfa77f8c02f951dc9d81b51672d7d0e 100644
174--- a/src/views/useHeaderConfigProps.tsx
175+++ b/src/views/useHeaderConfigProps.tsx
176@@ -397,6 +397,7 @@ export function useHeaderConfigProps({
177 // eslint-disable-next-line @eslint-react/no-array-index-key
178 key={index}
179 hidesSharedBackground={item.hidesSharedBackground}
180+ identifier={item.identifier}
181 >
182 {item.element}
183 </ScreenStackHeaderLeftView>
184@@ -471,6 +472,7 @@ export function useHeaderConfigProps({
185 // eslint-disable-next-line @eslint-react/no-array-index-key
186 key={index}
187 hidesSharedBackground={item.hidesSharedBackground}
188+ identifier={item.identifier}
189 >
190 {item.element}
191 </ScreenStackHeaderRightView>
patches/react-native-screens@4.25.2.patch+202-77
...@@ -37,6 +37,23 @@ index 03d8d9f3a5c403c93282d53cbacf1a339e877e85..54d5eabc6c8a408e711a5be97286fdbb...@@ -37,6 +37,23 @@ index 03d8d9f3a5c403c93282d53cbacf1a339e877e85..54d5eabc6c8a408e711a5be97286fdbb
37 override fun setTitleFontFamily(37 override fun setTitleFontFamily(
38 config: ScreenStackHeaderConfig,38 config: ScreenStackHeaderConfig,
39 titleFontFamily: String?,39 titleFontFamily: String?,
40diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt
41index 64f14f0c5ade16eb9519b8780d4ce013f88fbd3a..6cb1d69f97a95759c662a6006a9626925d9fefd9 100644
42--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt
43+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt
44@@ -47,6 +47,12 @@ class ScreenStackHeaderSubviewManager :
45 Log.w("[RNScreens]", "hidesSharedBackground prop is not available on Android")
46 }
47
48+ // Bar button transition identifiers only apply on iOS.
49+ override fun setIdentifier(
50+ view: ScreenStackHeaderSubview,
51+ identifier: String?,
52+ ) = Unit
53+
54 // synchronousShadowStateUpdatesEnabled is not available on Android atm,
55 // however we must override their setters
56 override fun setSynchronousShadowStateUpdatesEnabled(
40diff --git a/ios/RNSBarButtonItem.h b/ios/RNSBarButtonItem.h57diff --git a/ios/RNSBarButtonItem.h b/ios/RNSBarButtonItem.h
41index ea5325ea8d17b1ddfa790ff8dab48ce83142d4d3..acd2fd7ceb7162ed300abc4d3c9f0c24f4d63898 10064458index ea5325ea8d17b1ddfa790ff8dab48ce83142d4d3..acd2fd7ceb7162ed300abc4d3c9f0c24f4d63898 100644
42--- a/ios/RNSBarButtonItem.h59--- a/ios/RNSBarButtonItem.h
...@@ -140,10 +157,10 @@ index 919b984edc9f91ee9ac26faf257d8a721e26457c..5bb0cd6736ed6bc51db57e2a9326f758...@@ -140,10 +157,10 @@ index 919b984edc9f91ee9ac26faf257d8a721e26457c..5bb0cd6736ed6bc51db57e2a9326f758
140 157
141 NS_ASSUME_NONNULL_END158 NS_ASSUME_NONNULL_END
142diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm159diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm
143index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb2199dcbd58 100644160index 5970e3e3a624b9498b8dedfc16831df03a274d0c..c23776376110b6eda77072b134da29428f4bcba3 100644
144--- a/ios/RNSScreenStackHeaderConfig.mm161--- a/ios/RNSScreenStackHeaderConfig.mm
145+++ b/ios/RNSScreenStackHeaderConfig.mm162+++ b/ios/RNSScreenStackHeaderConfig.mm
146@@ -25,11 +25,33 @@163@@ -25,11 +25,46 @@
147 #import "RNSSearchBar.h"164 #import "RNSSearchBar.h"
148 #import "UINavigationBar+RNSUtility.h"165 #import "UINavigationBar+RNSUtility.h"
149 166
...@@ -153,10 +170,23 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -153,10 +170,23 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
153 170
154 static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17;171 static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17;
155+172+
156+// Keys for the last-applied JS bar button configs, associated with the173+#if !TARGET_OS_TV
157+// navigation item so unrelated header updates (title, subtitle, tint) don't174+// Each placement keeps its own cache so changes to one side do not move live
158+// recreate the native buttons they configure.175+// items on the other side into new groups during a navigation transition.
159+static char RNSAppliedHeaderBarButtonConfigsKey;176+static char RNSAppliedLeadingHeaderItemsKey;
177+static char RNSAppliedTrailingHeaderItemsKey;
178+static char RNSAppliedCenterHeaderItemsKey;
179+
180+// Records a placement's owner and contents, returning whether they changed.
181+static BOOL RNSUpdateHeaderItemsCache(UINavigationItem *navitem, const void *key, NSArray *itemsKey)
182+{
183+ if ([objc_getAssociatedObject(navitem, key) isEqual:itemsKey]) {
184+ return NO;
185+ }
186+ objc_setAssociatedObject(navitem, key, itemsKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
187+ return YES;
188+}
189+#endif
160+static char RNSAppliedToolbarConfigsKey;190+static char RNSAppliedToolbarConfigsKey;
161 static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34;191 static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34;
162 192
...@@ -177,7 +207,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -177,7 +207,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
177 @interface RCTImageLoader (Private)207 @interface RCTImageLoader (Private)
178 - (id<RCTImageCache>)imageCache;208 - (id<RCTImageCache>)imageCache;
179 @end209 @end
180@@ -47,6 +69,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string210@@ -47,6 +82,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string
181 @end211 @end
182 212
183 @interface RNSScreenStackHeaderConfig () <RCTMountingTransactionObserving>213 @interface RNSScreenStackHeaderConfig () <RCTMountingTransactionObserving>
...@@ -187,7 +217,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -187,7 +217,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
187 @end217 @end
188 218
189 @implementation RNSScreenStackHeaderConfig {219 @implementation RNSScreenStackHeaderConfig {
190@@ -81,6 +106,7 @@ - (void)initProps220@@ -81,6 +119,7 @@ - (void)initProps
191 self.hidden = YES;221 self.hidden = YES;
192 _reactSubviews = [NSMutableArray new];222 _reactSubviews = [NSMutableArray new];
193 _backTitleVisible = YES;223 _backTitleVisible = YES;
...@@ -195,7 +225,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -195,7 +225,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
195 _blurEffect = RNSBlurEffectStyleNone;225 _blurEffect = RNSBlurEffectStyleNone;
196 }226 }
197 227
198@@ -496,6 +522,10 @@ + (void)updateViewController:(UIViewController *)vc228@@ -496,6 +535,10 @@ + (void)updateViewController:(UIViewController *)vc
199 229
200 if (shouldHide) {230 if (shouldHide) {
201 navitem.title = config.title;231 navitem.title = config.title;
...@@ -206,7 +236,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -206,7 +236,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
206 236
207 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items.237 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items.
208 [navctr setNavigationBarHidden:YES animated:animated];238 [navctr setNavigationBarHidden:YES animated:animated];
209@@ -512,11 +542,19 @@ + (void)updateViewController:(UIViewController *)vc239@@ -512,11 +555,19 @@ + (void)updateViewController:(UIViewController *)vc
210 }240 }
211 navitem.largeTitleDisplayMode =241 navitem.largeTitleDisplayMode =
212 config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;242 config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;
...@@ -226,19 +256,19 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -226,19 +256,19 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
226 256
227 // appearance does not apply to the tvOS so we need to use lagacy customization257 // appearance does not apply to the tvOS so we need to use lagacy customization
228 #if TARGET_OS_TV258 #if TARGET_OS_TV
229@@ -550,31 +588,25 @@ + (void)updateViewController:(UIViewController *)vc259@@ -548,8 +599,6 @@ + (void)updateViewController:(UIViewController *)vc
260 navitem.leftItemsSupplementBackButton = config.backButtonInCustomView;
261 #endif
230 navitem.titleView = nil;262 navitem.titleView = nil;
231- navitem.leftBarButtonItems = nil;263- navitem.leftBarButtonItems = nil;
232- navitem.rightBarButtonItems = nil;264- navitem.rightBarButtonItems = nil;
233-265
234+
235 #if !TARGET_OS_TV266 #if !TARGET_OS_TV
236 // We want to set navitem.searchController to nil only if we are sure267 // We want to set navitem.searchController to nil only if we are sure
237 // that we are removing the search bar from the header.268@@ -557,22 +606,18 @@ + (void)updateViewController:(UIViewController *)vc
238 bool searchBarPresent = false;269 bool searchBarPresent = false;
239 #endif /* !TARGET_OS_TV */270 #endif /* !TARGET_OS_TV */
240-271
241+
242+ NSMutableArray<UIBarButtonItem *> *subviewLeftItems = [NSMutableArray array];272+ NSMutableArray<UIBarButtonItem *> *subviewLeftItems = [NSMutableArray array];
243+ NSMutableArray<UIBarButtonItem *> *subviewRightItems = [NSMutableArray array];273+ NSMutableArray<UIBarButtonItem *> *subviewRightItems = [NSMutableArray array];
244 for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {274 for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
...@@ -262,9 +292,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -262,9 +292,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
262 break;292 break;
263 }293 }
264 case RNSScreenStackHeaderSubviewTypeCenter:294 case RNSScreenStackHeaderSubviewTypeCenter:
265 case RNSScreenStackHeaderSubviewTypeTitle: {295@@ -637,10 +682,453 @@ + (void)updateViewController:(UIViewController *)vc
266 navitem.titleView = subview;
267@@ -637,10 +669,467 @@ + (void)updateViewController:(UIViewController *)vc
268 // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug).296 // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug).
269 // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments)297 // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments)
270 navitem.title = config.title;298 navitem.title = config.title;
...@@ -272,9 +300,6 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -272,9 +300,6 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
272- withCurrentItems:navitem.leftBarButtonItems];300- withCurrentItems:navitem.leftBarButtonItems];
273- navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems301- navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
274- withCurrentItems:navitem.rightBarButtonItems];302- withCurrentItems:navitem.rightBarButtonItems];
275+ NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[];
276+ NSArray *headerRightConfigs = config.headerRightBarButtonItems ?: @[];
277+ NSArray *headerCenterConfigs = config.headerCenterBarButtonItems ?: @[];
278+ // Retain the config in the key so its address cannot be reused while cached.303+ // Retain the config in the key so its address cannot be reused while cached.
279+ // Items capture this config's event emitter in their press handlers, so a304+ // Items capture this config's event emitter in their press handlers, so a
280+ // remounted header-config view with value-equal configs must still rebuild305+ // remounted header-config view with value-equal configs must still rebuild
...@@ -283,59 +308,48 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -283,59 +308,48 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
283+ // subview. Include those native identities in the key so React-backed308+ // subview. Include those native identities in the key so React-backed
284+ // items can use the same cache without reusing a group after its view was309+ // items can use the same cache without reusing a group after its view was
285+ // replaced.310+ // replaced.
286+ NSArray *headerItemsKey = @[311+ // A bar button item can belong to only one group. Even if the item itself
287+ config,312+ // is reused, constructing a new group removes it from the old group that
288+ headerLeftConfigs,313+ // UIKit may still be animating. Preserve each unchanged placement's group,
289+ headerRightConfigs,314+ // including the empty leading group beside the system back button.
290+ headerCenterConfigs,
291+ subviewLeftItems,
292+ subviewRightItems,
293+ ];
294+ // Rebuilding bar button items creates brand-new native buttons (glass
295+ // UIButton custom views on iOS 26). Replacing them while UIKit animates an
296+ // existing one (menu capsule morph, push/pop glass transitions) strands the
297+ // animation overlay — a stuck expanded capsule or an unmasked square back
298+ // button. When both the JS configs and React-backed item identities are
299+ // unchanged, keep the already-applied item groups. Item groups are only
300+ // available from iOS 16, so older platforms keep the legacy rebuild path.
301+ BOOL canReuseHeaderBarButtonItems = NO;
302+#if !TARGET_OS_TV315+#if !TARGET_OS_TV
303+ if (@available(iOS 16.0, *)) {316+ if (@available(iOS 16.0, *)) {
304+ canReuseHeaderBarButtonItems = YES;317+ NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[];
305+ }318+ NSArray *headerRightConfigs = config.headerRightBarButtonItems ?: @[];
319+ NSArray *headerCenterConfigs = config.headerCenterBarButtonItems ?: @[];
320+ if (RNSUpdateHeaderItemsCache(
321+ navitem, &RNSAppliedLeadingHeaderItemsKey, @[ config, headerLeftConfigs, subviewLeftItems ])) {
322+ NSArray *items = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems
323+ withCurrentItems:subviewLeftItems
324+ navigationItem:navitem];
325+ navitem.leadingItemGroups = [config barButtonItemGroupsFromItems:items];
326+ }
327+ if (RNSUpdateHeaderItemsCache(
328+ navitem, &RNSAppliedTrailingHeaderItemsKey, @[ config, headerRightConfigs, subviewRightItems ])) {
329+ NSArray *items = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
330+ withCurrentItems:subviewRightItems
331+ navigationItem:navitem];
332+ navitem.trailingItemGroups = [config barButtonItemGroupsFromItems:items];
333+ }
334+ if (@available(iOS 26.0, *)) {
335+ if (RNSUpdateHeaderItemsCache(navitem, &RNSAppliedCenterHeaderItemsKey, @[ config, headerCenterConfigs ])) {
336+ NSArray *items = [config barButtonItemsFromConfigs:config.headerCenterBarButtonItems
337+ withCurrentItems:@[]
338+ navigationItem:navitem];
339+ navitem.centerItemGroups = [config barButtonItemGroupsFromItems:items];
340+ }
341+ }
342+ } else
306+#endif343+#endif
307+ BOOL reuseHeaderBarButtonItems = canReuseHeaderBarButtonItems &&344+ {
308+ [objc_getAssociatedObject(navitem, &RNSAppliedHeaderBarButtonConfigsKey) isEqual:headerItemsKey];
309+ if (!reuseHeaderBarButtonItems) {
310+ NSArray<UIBarButtonItem *> *leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems345+ NSArray<UIBarButtonItem *> *leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems
311+ withCurrentItems:subviewLeftItems346+ withCurrentItems:subviewLeftItems
312+ navigationItem:navitem];347+ navigationItem:navitem];
313+ NSArray<UIBarButtonItem *> *rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems348+ NSArray<UIBarButtonItem *> *rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
314+ withCurrentItems:subviewRightItems349+ withCurrentItems:subviewRightItems
315+ navigationItem:navitem];350+ navigationItem:navitem];
316+ NSArray<UIBarButtonItem *> *centerBarButtonItems = [config barButtonItemsFromConfigs:config.headerCenterBarButtonItems
317+ withCurrentItems:@[]
318+ navigationItem:navitem];
319+#if !TARGET_OS_TV
320+ if (@available(iOS 16.0, *)) {
321+ navitem.leadingItemGroups = [config barButtonItemGroupsFromItems:leftBarButtonItems];
322+ navitem.trailingItemGroups = [config barButtonItemGroupsFromItems:rightBarButtonItems];
323+ if (@available(iOS 26.0, *)) {
324+ navitem.centerItemGroups = [config barButtonItemGroupsFromItems:centerBarButtonItems];
325+ }
326+ } else {
327+ navitem.leftBarButtonItems = leftBarButtonItems;
328+ navitem.rightBarButtonItems = rightBarButtonItems;
329+ }
330+#else
331+ navitem.leftBarButtonItems = leftBarButtonItems;351+ navitem.leftBarButtonItems = leftBarButtonItems;
332+ navitem.rightBarButtonItems = rightBarButtonItems;352+ navitem.rightBarButtonItems = rightBarButtonItems;
333+#endif
334+ objc_setAssociatedObject(
335+ navitem,
336+ &RNSAppliedHeaderBarButtonConfigsKey,
337+ headerItemsKey,
338+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
339+ }353+ }
340+ NSDictionary<NSString *, id> *mailSearchToolbarConfig = nil;354+ NSDictionary<NSString *, id> *mailSearchToolbarConfig = nil;
341+ for (NSDictionary<NSString *, id> *toolbarConfig in config.headerToolbarItems) {355+ for (NSDictionary<NSString *, id> *toolbarConfig in config.headerToolbarItems) {
...@@ -736,7 +750,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -736,7 +750,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
736 750
737 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items751 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items
738 // (setting nav bar visibility should be done after `navitem.*BarButtonItems`).752 // (setting nav bar visibility should be done after `navitem.*BarButtonItems`).
739@@ -773,6 +1262,7 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *753@@ -773,6 +1261,7 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
740 754
741 - (NSArray<UIBarButtonItem *> *)barButtonItemsFromConfigs:(NSArray<NSDictionary<NSString *, id> *> *)dicts755 - (NSArray<UIBarButtonItem *> *)barButtonItemsFromConfigs:(NSArray<NSDictionary<NSString *, id> *> *)dicts
742 withCurrentItems:(NSArray<UIBarButtonItem *> *)currentItems756 withCurrentItems:(NSArray<UIBarButtonItem *> *)currentItems
...@@ -744,7 +758,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -744,7 +758,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
744 {758 {
745 if (dicts.count == 0) {759 if (dicts.count == 0) {
746 return currentItems;760 return currentItems;
747@@ -781,7 +1271,197 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *761@@ -781,7 +1270,197 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
748 [items addObjectsFromArray:currentItems];762 [items addObjectsFromArray:currentItems];
749 for (NSUInteger i = 0; i < dicts.count; i++) {763 for (NSUInteger i = 0; i < dicts.count; i++) {
750 NSDictionary *dict = dicts[i];764 NSDictionary *dict = dicts[i];
...@@ -943,7 +957,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -943,7 +957,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
943 RNSBarButtonItem *item = [[RNSBarButtonItem alloc] initWithConfig:dict957 RNSBarButtonItem *item = [[RNSBarButtonItem alloc] initWithConfig:dict
944 action:^(NSString *buttonId) {958 action:^(NSString *buttonId) {
945 auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(959 auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
946@@ -803,19 +1483,23 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *960@@ -803,19 +1482,23 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
947 }961 }
948 imageLoader:_imageLoader];962 imageLoader:_imageLoader];
949 NSNumber *index = dict[@"index"];963 NSNumber *index = dict[@"index"];
...@@ -973,7 +987,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -973,7 +987,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
973 [items insertObject:item atIndex:index.integerValue];987 [items insertObject:item atIndex:index.integerValue];
974 } else {988 } else {
975 [items addObject:item];989 [items addObject:item];
976@@ -825,6 +1509,47 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *990@@ -825,6 +1508,47 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
977 return items;991 return items;
978 }992 }
979 993
...@@ -1021,7 +1035,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -1021,7 +1035,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
1021 RNS_IGNORE_SUPER_CALL_BEGIN1035 RNS_IGNORE_SUPER_CALL_BEGIN
1022 - (void)insertReactSubview:(RNSScreenStackHeaderSubview *)subview atIndex:(NSInteger)atIndex1036 - (void)insertReactSubview:(RNSScreenStackHeaderSubview *)subview atIndex:(NSInteger)atIndex
1023 {1037 {
1024@@ -1013,6 +1738,8 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::1038@@ -1013,6 +1737,8 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
1025 }1039 }
1026 1040
1027 _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title);1041 _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title);
...@@ -1030,7 +1044,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -1030,7 +1044,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
1030 if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) {1044 if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) {
1031 _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily);1045 _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily);
1032 }1046 }
1033@@ -1038,6 +1765,7 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::1047@@ -1038,6 +1764,7 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
1034 _disableBackButtonMenu = newScreenProps.disableBackButtonMenu;1048 _disableBackButtonMenu = newScreenProps.disableBackButtonMenu;
1035 _backButtonDisplayMode =1049 _backButtonDisplayMode =
1036 [RNSConvert UINavigationItemBackButtonDisplayModeFromCppEquivalent:newScreenProps.backButtonDisplayMode];1050 [RNSConvert UINavigationItemBackButtonDisplayModeFromCppEquivalent:newScreenProps.backButtonDisplayMode];
...@@ -1038,7 +1052,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -1038,7 +1052,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
1038 1052
1039 if (newScreenProps.userInterfaceStyle != oldScreenProps.userInterfaceStyle) {1053 if (newScreenProps.userInterfaceStyle != oldScreenProps.userInterfaceStyle) {
1040 _userInterfaceStyle = [RNSConvert UIUserInterfaceStyleFromCppEquivalent:newScreenProps.userInterfaceStyle];1054 _userInterfaceStyle = [RNSConvert UIUserInterfaceStyleFromCppEquivalent:newScreenProps.userInterfaceStyle];
1041@@ -1084,6 +1812,30 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::1055@@ -1084,6 +1811,30 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
1042 _headerRightBarButtonItems = array;1056 _headerRightBarButtonItems = array;
1043 }1057 }
1044 1058
...@@ -1069,6 +1083,65 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21...@@ -1069,6 +1083,65 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
1069 [self updateViewControllerIfNeeded];1083 [self updateViewControllerIfNeeded];
1070 1084
1071 if (needsNavigationControllerLayout) {1085 if (needsNavigationControllerLayout) {
1086diff --git a/ios/RNSScreenStackHeaderSubview.mm b/ios/RNSScreenStackHeaderSubview.mm
1087index 15c0da5242372c4be833a1403663bd1dfa244362..d1f838cdfd831abd6397be6fe61e00a50f87bea3 100644
1088--- a/ios/RNSScreenStackHeaderSubview.mm
1089+++ b/ios/RNSScreenStackHeaderSubview.mm
1090@@ -21,6 +21,7 @@ @implementation RNSScreenStackHeaderSubview {
1091 // This is a strong reference to UIBarButtonItem which creates a retain cycle.
1092 // The cycle is cleared via `invalidateUIBarButtonItem` method, called by `invalidate` callback.
1093 UIBarButtonItem *_barButtonItem;
1094+ NSString *_barButtonItemIdentifier;
1095 BOOL _hidesSharedBackground;
1096 }
1097
1098@@ -128,6 +129,7 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
1099 const auto &newHeaderSubviewProps = *std::static_pointer_cast<const react::RNSScreenStackHeaderSubviewProps>(props);
1100
1101 [self setType:[RNSConvert RNSScreenStackHeaderSubviewTypeFromCppEquivalent:newHeaderSubviewProps.type]];
1102+ [self setBarButtonItemIdentifier:RCTNSStringFromStringNilIfEmpty(newHeaderSubviewProps.identifier)];
1103 [self setHidesSharedBackground:newHeaderSubviewProps.hidesSharedBackground];
1104 [self setSynchronousShadowStateUpdatesEnabled:newHeaderSubviewProps.synchronousShadowStateUpdatesEnabled];
1105 [super updateProps:props oldProps:oldProps];
1106@@ -281,7 +283,14 @@ - (void)configureBarButtonItem
1107 #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
1108 if (@available(iOS 26.0, *)) {
1109 if (_barButtonItem != nil) {
1110- [_barButtonItem setHidesSharedBackground:_hidesSharedBackground];
1111+ NSString *currentIdentifier = _barButtonItem.identifier;
1112+ if (currentIdentifier != _barButtonItemIdentifier &&
1113+ ![currentIdentifier isEqualToString:_barButtonItemIdentifier]) {
1114+ _barButtonItem.identifier = _barButtonItemIdentifier;
1115+ }
1116+ if (_barButtonItem.hidesSharedBackground != _hidesSharedBackground) {
1117+ _barButtonItem.hidesSharedBackground = _hidesSharedBackground;
1118+ }
1119 }
1120 }
1121 #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
1122@@ -289,10 +298,22 @@ - (void)configureBarButtonItem
1123
1124 - (void)setHidesSharedBackground:(BOOL)hidesSharedBackground
1125 {
1126+ if (_hidesSharedBackground == hidesSharedBackground) {
1127+ return;
1128+ }
1129 _hidesSharedBackground = hidesSharedBackground;
1130 [self configureBarButtonItem];
1131 }
1132
1133+- (void)setBarButtonItemIdentifier:(nullable NSString *)identifier
1134+{
1135+ if (_barButtonItemIdentifier == identifier || [_barButtonItemIdentifier isEqualToString:identifier]) {
1136+ return;
1137+ }
1138+ _barButtonItemIdentifier = [identifier copy];
1139+ [self configureBarButtonItem];
1140+}
1141+
1142 #pragma mark - Dynamic frameworks support
1143
1144 // Needed because of this: https://github.com/facebook/react-native/pull/37274
1072diff --git a/lib/commonjs/components/ScreenStackHeaderConfig.js b/lib/commonjs/components/ScreenStackHeaderConfig.js1145diff --git a/lib/commonjs/components/ScreenStackHeaderConfig.js b/lib/commonjs/components/ScreenStackHeaderConfig.js
1073index 16b979bb3dfb41ff247403f1632c300a9a60d549..01415d4cba66086a8d8e5af728f809aee5190d91 1006441146index 16b979bb3dfb41ff247403f1632c300a9a60d549..01415d4cba66086a8d8e5af728f809aee5190d91 100644
1074--- a/lib/commonjs/components/ScreenStackHeaderConfig.js1147--- a/lib/commonjs/components/ScreenStackHeaderConfig.js
...@@ -1347,8 +1420,20 @@ index b7568ecfebd4f4420f2a8d3fec5184d7fc728dd1..41ca1f273f0175929e73105faa463978...@@ -1347,8 +1420,20 @@ index b7568ecfebd4f4420f2a8d3fec5184d7fc728dd1..41ca1f273f0175929e73105faa463978
1347 onPressHeaderBarButtonItem?: CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent> | undefined;1420 onPressHeaderBarButtonItem?: CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent> | undefined;
1348 onPressHeaderBarButtonMenuItem?: CT.DirectEventHandler<OnPressHeaderBarButtonMenuItemEvent> | undefined;1421 onPressHeaderBarButtonMenuItem?: CT.DirectEventHandler<OnPressHeaderBarButtonMenuItemEvent> | undefined;
1349 synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, false>;1422 synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, false>;
1423diff --git a/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts b/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts
1424index 1da0ea65ede8082de83884dd38c7c46b1988db67..1168b5d031efd3f796bb4448c1128e14de70f3fa 100644
1425--- a/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts
1426+++ b/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts
1427@@ -3,6 +3,7 @@ export type HeaderSubviewTypes = 'back' | 'right' | 'left' | 'title' | 'center'
1428 export interface NativeProps extends ViewProps {
1429 type?: CT.WithDefault<HeaderSubviewTypes, 'left'>;
1430 hidesSharedBackground?: boolean | undefined;
1431+ identifier?: string | undefined;
1432 synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, false>;
1433 }
1434 declare const _default: import("react-native").HostComponent<NativeProps>;
1350diff --git a/lib/typescript/types.d.ts b/lib/typescript/types.d.ts1435diff --git a/lib/typescript/types.d.ts b/lib/typescript/types.d.ts
1351index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503dbe6ee9840 1006441436index 3b384e03891e38e936f370372a682d73440e7ec2..f8ec3b46a92bf20e2845eb5867af5c372215a532 100644
1352--- a/lib/typescript/types.d.ts1437--- a/lib/typescript/types.d.ts
1353+++ b/lib/typescript/types.d.ts1438+++ b/lib/typescript/types.d.ts
1354@@ -10,6 +10,7 @@ export type SearchBarCommands = {1439@@ -10,6 +10,7 @@ export type SearchBarCommands = {
...@@ -1422,7 +1507,21 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db...@@ -1422,7 +1507,21 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db
1422 /**1507 /**
1423 * Allows for setting text color of the title.1508 * Allows for setting text color of the title.
1424 */1509 */
1425@@ -1001,6 +1044,11 @@ interface SharedHeaderBarButtonItem {1510@@ -963,6 +1006,13 @@ export interface SearchBarProps {
1511 shouldShowHintSearchIcon?: boolean | undefined;
1512 }
1513 export interface ScreenStackHeaderSubviewProps {
1514+ /**
1515+ * An identifier used to match this item across navigation bar transitions.
1516+ * Only applicable to type="right" and type="left" subviews on iOS 26.0 and later.
1517+ *
1518+ * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier
1519+ */
1520+ identifier?: string | undefined;
1521 /**
1522 * A boolean value indicating whether the background this item may share with other items in the bar should be hidden.
1523 * Only applicable to type="right" and type="left" subviews.
1524@@ -1001,6 +1051,11 @@ interface SharedHeaderBarButtonItem {
1426 * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property1525 * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property
1427 */1526 */
1428 variant?: 'plain' | 'done' | 'prominent' | undefined;1527 variant?: 'plain' | 'done' | 'prominent' | undefined;
...@@ -1434,7 +1533,7 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db...@@ -1434,7 +1533,7 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db
1434 /**1533 /**
1435 * The tint color to apply to the item.1534 * The tint color to apply to the item.
1436 *1535 *
1437@@ -1145,8 +1193,38 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {1536@@ -1145,8 +1200,38 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
1438 export interface HeaderBarButtonItemSpacing {1537 export interface HeaderBarButtonItemSpacing {
1439 type: 'spacing';1538 type: 'spacing';
1440 spacing: number;1539 spacing: number;
...@@ -1688,8 +1787,20 @@ index ba2479de0f49c112470f78c5084059ddd9e2f3e8..8677583a8f8daa4839340467466bfab8...@@ -1688,8 +1787,20 @@ index ba2479de0f49c112470f78c5084059ddd9e2f3e8..8677583a8f8daa4839340467466bfab8
1688 onPressHeaderBarButtonItem?:1787 onPressHeaderBarButtonItem?:
1689 | CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent>1788 | CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent>
1690 | undefined;1789 | undefined;
1790diff --git a/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts b/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts
1791index 9b7832bff0e1c8226ed652d3c90b3a330c6d9b20..17a7eace328f9a825c570a92d549d69195e94568 100644
1792--- a/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts
1793+++ b/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts
1794@@ -14,6 +14,7 @@ export type HeaderSubviewTypes =
1795 export interface NativeProps extends ViewProps {
1796 type?: CT.WithDefault<HeaderSubviewTypes, 'left'>;
1797 hidesSharedBackground?: boolean | undefined;
1798+ identifier?: string | undefined;
1799 synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, false>;
1800 }
1801
1691diff --git a/src/types.tsx b/src/types.tsx1802diff --git a/src/types.tsx b/src/types.tsx
1692index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e131d21242b 1006441803index 76a83f3acb6fd3f0af7f027798848b7124100286..39f0ae8065f4dc99a1571a3fc1cb3398caf03ff7 100644
1693--- a/src/types.tsx1804--- a/src/types.tsx
1694+++ b/src/types.tsx1805+++ b/src/types.tsx
1695@@ -26,6 +26,7 @@ export type SearchBarCommands = {1806@@ -26,6 +26,7 @@ export type SearchBarCommands = {
...@@ -1763,7 +1874,21 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13...@@ -1763,7 +1874,21 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13
1763 /**1874 /**
1764 * Allows for setting text color of the title.1875 * Allows for setting text color of the title.
1765 */1876 */
1766@@ -1125,6 +1168,11 @@ interface SharedHeaderBarButtonItem {1877@@ -1084,6 +1127,13 @@ export interface SearchBarProps {
1878 }
1879
1880 export interface ScreenStackHeaderSubviewProps {
1881+ /**
1882+ * An identifier used to match this item across navigation bar transitions.
1883+ * Only applicable to type="right" and type="left" subviews on iOS 26.0 and later.
1884+ *
1885+ * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier
1886+ */
1887+ identifier?: string | undefined;
1888 /**
1889 * A boolean value indicating whether the background this item may share with other items in the bar should be hidden.
1890 * Only applicable to type="right" and type="left" subviews.
1891@@ -1125,6 +1175,11 @@ interface SharedHeaderBarButtonItem {
1767 * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property1892 * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property
1768 */1893 */
1769 variant?: 'plain' | 'done' | 'prominent' | undefined;1894 variant?: 'plain' | 'done' | 'prominent' | undefined;
...@@ -1775,7 +1900,7 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13...@@ -1775,7 +1900,7 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13
1775 /**1900 /**
1776 * The tint color to apply to the item.1901 * The tint color to apply to the item.
1777 *1902 *
1778@@ -1279,11 +1327,47 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {1903@@ -1279,11 +1334,47 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
1779 export interface HeaderBarButtonItemSpacing {1904 export interface HeaderBarButtonItemSpacing {
1780 type: 'spacing';1905 type: 'spacing';
1781 spacing: number;1906 spacing: number;
pnpm-lock.yaml+16-16
...@@ -88,13 +88,13 @@ patchedDependencies:...@@ -88,13 +88,13 @@ patchedDependencies:
88 '@pierre/diffs@1.3.0-beta.10': 7ef7cb0cbabb17c15cdb137554068b36f15f6f5265e73fb51452aa3380db91aa88 '@pierre/diffs@1.3.0-beta.10': 7ef7cb0cbabb17c15cdb137554068b36f15f6f5265e73fb51452aa3380db91aa
89 '@react-native-menu/menu@2.0.0': c7f66d121c726ade4f5c4e1aed11a691e5711d244c544084e289ac26132a004589 '@react-native-menu/menu@2.0.0': c7f66d121c726ade4f5c4e1aed11a691e5711d244c544084e289ac26132a0045
90 '@react-native/gradle-plugin@0.85.3': c1b594a16e682d621b6a960926f8ce13fc92edfb397884cfe7fcb0518996a78490 '@react-native/gradle-plugin@0.85.3': c1b594a16e682d621b6a960926f8ce13fc92edfb397884cfe7fcb0518996a784
91 '@react-navigation/native-stack@7.17.6': 0365b727005b3a830af80ccbd0b637666cc0338d33ddcb7a25b6de51a21ea02791 '@react-navigation/native-stack@7.17.6': e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552
92 effect@4.0.0-beta.103: af36b7948b6f9c56623074662b51dade5699880c1a7c71245de73e13c3185fb692 effect@4.0.0-beta.103: af36b7948b6f9c56623074662b51dade5699880c1a7c71245de73e13c3185fb6
93 expo-modules-jsi@56.0.10: 9170f8074ae4e35a0a086e756c8f815794fd3abe51eac67ca3ba02804225ec1f93 expo-modules-jsi@56.0.10: 9170f8074ae4e35a0a086e756c8f815794fd3abe51eac67ca3ba02804225ec1f
94 react-native-gesture-handler@2.31.2: 808eb26f9e57cf4945efd3985af4d9c764da6f91f4c9764433cc868602bbf4d394 react-native-gesture-handler@2.31.2: 808eb26f9e57cf4945efd3985af4d9c764da6f91f4c9764433cc868602bbf4d3
95 react-native-keyboard-controller@1.21.13: 20be72c84d74253acdcfefbc6defe36dc396944f1a44cab2bdd0e3cd572ae00895 react-native-keyboard-controller@1.21.13: 20be72c84d74253acdcfefbc6defe36dc396944f1a44cab2bdd0e3cd572ae008
96 react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b608842267596 react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675
97 react-native-screens@4.25.2: b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc97 react-native-screens@4.25.2: 7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e
9898
99importers:99importers:
100100
...@@ -248,7 +248,7 @@ importers:...@@ -248,7 +248,7 @@ importers:
248 version: 7.3.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6250 specifier: 7.17.6
251 version: 7.17.6(patch_hash=0365b727005b3a830af80ccbd0b637666cc0338d33ddcb7a25b6de51a21ea027)(e49ba4adb65ddb2cde0c8b8b274b705b)251 version: 7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(8effe605a545f8a17acafbaed58f0e86)
252 '@shikijs/core':252 '@shikijs/core':
253 specifier: 4.2.0253 specifier: 4.2.0
254 version: 4.2.0254 version: 4.2.0
...@@ -416,7 +416,7 @@ importers:...@@ -416,7 +416,7 @@ importers:
416 version: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.25.2418 specifier: 4.25.2
419 version: 4.25.2(patch_hash=b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.25.2(patch_hash=7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.12421 specifier: ^0.3.12
422 version: 0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
...@@ -12252,7 +12252,7 @@ snapshots:...@@ -12252,7 +12252,7 @@ snapshots:
12252 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)12252 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
12253 zod: 3.25.7612253 zod: 3.25.76
12254 optionalDependencies:12254 optionalDependencies:
12255 expo-router: 56.2.11(b91ff5333bc046aa08fb7484e4e065d6)12255 expo-router: 56.2.11(1d12f8130d9562de288829ccb10045a7)
12256 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)12256 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
12257 transitivePeerDependencies:12257 transitivePeerDependencies:
12258 - '@expo/dom-webview'12258 - '@expo/dom-webview'
...@@ -12328,7 +12328,7 @@ snapshots:...@@ -12328,7 +12328,7 @@ snapshots:
12328 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)12328 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
12329 zod: 3.25.7612329 zod: 3.25.76
12330 optionalDependencies:12330 optionalDependencies:
12331 expo-router: 56.2.11(f8b3939b2a15129a8b59814542a58c1b)12331 expo-router: 56.2.11(5cef6fdc582974f4d93d5694873ec814)
12332 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6)12332 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6)
12333 transitivePeerDependencies:12333 transitivePeerDependencies:
12334 - '@expo/dom-webview'12334 - '@expo/dom-webview'
...@@ -12668,7 +12668,7 @@ snapshots:...@@ -12668,7 +12668,7 @@ snapshots:
12668 react: 19.2.312668 react: 19.2.3
12669 optionalDependencies:12669 optionalDependencies:
12670 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)12670 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
12671 expo-router: 56.2.11(b91ff5333bc046aa08fb7484e4e065d6)12671 expo-router: 56.2.11(1d12f8130d9562de288829ccb10045a7)
12672 react-dom: 19.2.3(react@19.2.3)12672 react-dom: 19.2.3(react@19.2.3)
12673 transitivePeerDependencies:12673 transitivePeerDependencies:
12674 - supports-color12674 - supports-color
...@@ -12683,7 +12683,7 @@ snapshots:...@@ -12683,7 +12683,7 @@ snapshots:
12683 react: 19.2.612683 react: 19.2.6
12684 optionalDependencies:12684 optionalDependencies:
12685 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.6(react@19.2.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)12685 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.6(react@19.2.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)
12686 expo-router: 56.2.11(f8b3939b2a15129a8b59814542a58c1b)12686 expo-router: 56.2.11(5cef6fdc582974f4d93d5694873ec814)
12687 react-dom: 19.2.6(react@19.2.6)12687 react-dom: 19.2.6(react@19.2.6)
12688 transitivePeerDependencies:12688 transitivePeerDependencies:
12689 - supports-color12689 - supports-color
...@@ -14268,7 +14268,7 @@ snapshots:...@@ -14268,7 +14268,7 @@ snapshots:
14268 optionalDependencies:14268 optionalDependencies:
14269 '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)14269 '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
1427014270
14271 '@react-navigation/native-stack@7.17.6(patch_hash=0365b727005b3a830af80ccbd0b637666cc0338d33ddcb7a25b6de51a21ea027)(e49ba4adb65ddb2cde0c8b8b274b705b)':14271 '@react-navigation/native-stack@7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(8effe605a545f8a17acafbaed58f0e86)':
14272 dependencies:14272 dependencies:
14273 '@react-navigation/elements': 2.9.26(c6a2ad0e2c930f8e3896e77c3ba11bc4)14273 '@react-navigation/elements': 2.9.26(c6a2ad0e2c930f8e3896e77c3ba11bc4)
14274 '@react-navigation/native': 7.3.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)14274 '@react-navigation/native': 7.3.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
...@@ -14276,7 +14276,7 @@ snapshots:...@@ -14276,7 +14276,7 @@ snapshots:
14276 react: 19.2.314276 react: 19.2.3
14277 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)14277 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
14278 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)14278 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
14279 react-native-screens: 4.25.2(patch_hash=b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)14279 react-native-screens: 4.25.2(patch_hash=7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
14280 sf-symbols-typescript: 2.2.014280 sf-symbols-typescript: 2.2.0
14281 warn-once: 0.1.114281 warn-once: 0.1.1
14282 transitivePeerDependencies:14282 transitivePeerDependencies:
...@@ -17062,7 +17062,7 @@ snapshots:...@@ -17062,7 +17062,7 @@ snapshots:
17062 - supports-color17062 - supports-color
17063 - typescript17063 - typescript
1706417064
17065 expo-router@56.2.11(b91ff5333bc046aa08fb7484e4e065d6):17065 expo-router@56.2.11(1d12f8130d9562de288829ccb10045a7):
17066 dependencies:17066 dependencies:
17067 '@expo/log-box': 56.0.13(@expo/dom-webview@56.0.5)(expo@56.0.12)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)17067 '@expo/log-box': 56.0.13(@expo/dom-webview@56.0.5)(expo@56.0.12)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
17068 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)17068 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
...@@ -17093,7 +17093,7 @@ snapshots:...@@ -17093,7 +17093,7 @@ snapshots:
17093 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)17093 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
17094 react-native-drawer-layout: 4.2.4(05364bd849de538917a7364cc7dee3f5)17094 react-native-drawer-layout: 4.2.4(05364bd849de538917a7364cc7dee3f5)
17095 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)17095 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
17096 react-native-screens: 4.25.2(patch_hash=b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)17096 react-native-screens: 4.25.2(patch_hash=7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
17097 server-only: 0.0.117097 server-only: 0.0.1
17098 sf-symbols-typescript: 2.2.017098 sf-symbols-typescript: 2.2.0
17099 shallowequal: 1.1.017099 shallowequal: 1.1.0
...@@ -17113,7 +17113,7 @@ snapshots:...@@ -17113,7 +17113,7 @@ snapshots:
17113 - supports-color17113 - supports-color
17114 optional: true17114 optional: true
1711517115
17116 expo-router@56.2.11(f8b3939b2a15129a8b59814542a58c1b):17116 expo-router@56.2.11(5cef6fdc582974f4d93d5694873ec814):
17117 dependencies:17117 dependencies:
17118 '@expo/log-box': 56.0.13(@expo/dom-webview@56.0.5)(expo@56.0.12)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)17118 '@expo/log-box': 56.0.13(@expo/dom-webview@56.0.5)(expo@56.0.12)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)
17119 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.6(react@19.2.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)17119 '@expo/metro-runtime': 56.0.15(@expo/log-box@56.0.13)(expo@56.0.12)(react-dom@19.2.6(react@19.2.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)
...@@ -17144,7 +17144,7 @@ snapshots:...@@ -17144,7 +17144,7 @@ snapshots:
17144 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6)17144 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6)
17145 react-native-drawer-layout: 4.2.4(de9b2f2dc96a3557fdc0df187a8417ee)17145 react-native-drawer-layout: 4.2.4(de9b2f2dc96a3557fdc0df187a8417ee)
17146 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)17146 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)
17147 react-native-screens: 4.25.2(patch_hash=b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)17147 react-native-screens: 4.25.2(patch_hash=7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6)
17148 server-only: 0.0.117148 server-only: 0.0.1
17149 sf-symbols-typescript: 2.2.017149 sf-symbols-typescript: 2.2.0
17150 shallowequal: 1.1.017150 shallowequal: 1.1.0
...@@ -19959,14 +19959,14 @@ snapshots:...@@ -19959,14 +19959,14 @@ snapshots:
19959 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6)19959 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6)
19960 optional: true19960 optional: true
1996119961
19962 react-native-screens@4.25.2(patch_hash=b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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):19962 react-native-screens@4.25.2(patch_hash=7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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):
19963 dependencies:19963 dependencies:
19964 react: 19.2.319964 react: 19.2.3
19965 react-freeze: 1.0.4(react@19.2.3)19965 react-freeze: 1.0.4(react@19.2.3)
19966 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)19966 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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)
19967 warn-once: 0.1.119967 warn-once: 0.1.1
1996819968
19969 react-native-screens@4.25.2(patch_hash=b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6):19969 react-native-screens@4.25.2(patch_hash=7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.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.6)(utf-8-validate@6.0.6))(react@19.2.6):
19970 dependencies:19970 dependencies:
19971 react: 19.2.619971 react: 19.2.6
19972 react-freeze: 1.0.4(react@19.2.6)19972 react-freeze: 1.0.4(react@19.2.6)