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";
1515const IOS_NATIVE_LEADING_TITLE_OFFSET = -6;
1616const 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
1821/**
1922 * Horizontal correction applied to content rendered in the brand title slot,
2023 * shared with the connection-status swap so both align identically.
......@@ -76,6 +79,7 @@ export function renderCompactBrandHeaderItems(): NativeStackHeaderItem[] {
7679 {
7780 element: <CompactBrandTitle nativeLeadingItem />,
7881 hidesSharedBackground: true,
82 identifier: BRAND_HEADER_ITEM_IDENTIFIER,
7983 type: "custom",
8084 },
8185 ];
apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx+6-1
......@@ -7,7 +7,11 @@ import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-na
77
88import { SymbolView } from "../../components/AppSymbol";
99import { 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";
1115import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
1216import { useWorkspaceState } from "../../state/workspace";
1317import {
......@@ -172,6 +176,7 @@ export function getConnectionAwareBrandHeaderOptions(opts: {
172176 />
173177 ),
174178 hidesSharedBackground: true,
179 identifier: BRAND_HEADER_ITEM_IDENTIFIER,
175180 type: "custom",
176181 },
177182 ],
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.
132132## Related
133133
134134- [Workspace layout](./workspace-layout.md), [Glossary](./glossary.md)
135- [Mobile navigation headers](./mobile-navigation.md)
135136- [Remote environments](./remote.md), [Server updates](./server-updates.md)
136137- [Resource telemetry](./resource-telemetry.md)
137138- [Product analytics](./product-analytics.md)
patches/@react-navigation%2Fnative-stack@7.17.6.patch+103-36
......@@ -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;
123diff --git a/lib/module/views/useHeaderConfigProps.js b/lib/module/views/useHeaderConfigProps.js
2index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b01175ec19220 100644
24index 9c21aa48cb580704aa17e59d9cdadcc612a1e4ff..a1b3a38368c5ef2257570f382e87df5d6f55b11e 100644
325--- a/lib/module/views/useHeaderConfigProps.js
426+++ b/lib/module/views/useHeaderConfigProps.js
5@@ -19,6 +19,12 @@
27@@ -19,6 +19,12 @@ const processBarButtonItems = (items, colors, fonts) => {
628 }
729 return item;
830 }
......@@ -15,7 +37,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
1537 if (item.type === 'button' || item.type === 'menu') {
1638 if (item.type === 'menu' && item.menu == null) {
1739 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) => {
1941 }
2042 return processedItem;
2143 }
......@@ -24,7 +46,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
2446 }).filter(item => item != null);
2547 };
2648 const transformIcon = icon => {
27@@ -158,8 +164,12 @@
49@@ -158,8 +164,12 @@ export function useHeaderConfigProps({
2850 headerBack,
2951 route,
3052 title,
......@@ -38,7 +60,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
3860 }) {
3961 const {
4062 direction
41@@ -255,6 +265,10 @@
63@@ -255,6 +265,10 @@ export function useHeaderConfigProps({
4264 tintColor,
4365 canGoBack
4466 });
......@@ -49,7 +71,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
4971 let rightItems = headerRightItems?.({
5072 tintColor,
5173 canGoBack
52@@ -264,6 +278,10 @@
74@@ -264,6 +278,10 @@ export function useHeaderConfigProps({
5375 // So we need to reverse them here to match the order
5476 rightItems = [...rightItems].reverse();
5577 }
......@@ -60,7 +82,14 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
6082 const children = /*#__PURE__*/_jsxs(_Fragment, {
6183 children: [Platform.OS === 'ios' ? /*#__PURE__*/_jsxs(_Fragment, {
6284 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 }
6493 return null;
6594 }) : headerLeftElement != null ? /*#__PURE__*/_jsx(ScreenStackHeaderLeftView, {
6695 children: headerLeftElement
......@@ -69,6 +98,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
6998+ if (item.type === 'custom') {
7099+ return /*#__PURE__*/_jsx(ScreenStackHeaderCenterView, {
71100+ hidesSharedBackground: item.hidesSharedBackground,
101+ identifier: item.identifier,
72102+ children: item.element
73103+ }, index);
74104+ }
......@@ -77,7 +107,15 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
77107 children: headerTitleElement
78108 }) : null]
79109 }) : /*#__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({
81119 largeTitleFontWeight,
82120 largeTitleHideShadow: headerLargeTitleShadowVisible === false,
83121 title: titleText,
......@@ -85,7 +123,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
85123 titleColor,
86124 titleFontFamily,
87125 titleFontSize,
88@@ -364,9 +391,12 @@
126@@ -364,6 +394,9 @@ export function useHeaderConfigProps({
89127 disableTopInsetApplication: !headerTopInsetEnabled,
90128 translucent: translucent === true,
91129 children,
......@@ -95,30 +133,59 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117
95133 headerLeftBarButtonItems: processBarButtonItems(leftItems, colors, fonts),
96134 headerRightBarButtonItems: processBarButtonItems(rightItems, colors, fonts),
97135 experimental_userInterfaceStyle: dark ? 'dark' : 'light'
98 };
99 }
100-//# sourceMappingURL=useHeaderConfigProps.js.map
101\ No newline at end of file
102+//# sourceMappingURL=useHeaderConfigProps.js.map
103diff --git a/lib/module/views/NativeStackView.native.js b/lib/module/views/NativeStackView.native.js
104index c342e90..5d3e440 100644
105--- a/lib/module/views/NativeStackView.native.js
106+++ b/lib/module/views/NativeStackView.native.js
107@@ -370,6 +370,17 @@ export function NativeStackView({
108 return /*#__PURE__*/_jsx(SafeAreaProviderCompat, {
109 children: /*#__PURE__*/_jsx(ScreenStack, {
110 style: styles.container,
111+ onFinishTransitioning: () => {
112+ // Surface UIKit's transition-completion callback to every route of
113+ // this navigator. Unlike transitionEnd, this also fires when a modal
114+ // finishes dismissing — where the presenting screen below receives no
115+ // appearance callbacks — and for a gesture-driven dismissal it fires
116+ // before the state pop, while the modal route is still the focused
117+ // one, so the event must not be targeted at a single route.
118+ navigation.emit({
119+ type: 'finishTransitioning'
120+ });
121+ },
122 children: state.routes.concat(state.preloadedRoutes).map((route, index) => {
123 const descriptor = descriptors[route.key] ?? preloadedDescriptors[route.key];
124 const isFocused = state.index === index;
136diff --git a/lib/typescript/src/types.d.ts b/lib/typescript/src/types.d.ts
137index 2f1351a89f0f2e67e854dc81e392198326b71834..ccf8556911fa1fa17588f06b56edb2151a712bf0 100644
138--- a/lib/typescript/src/types.d.ts
139+++ b/lib/typescript/src/types.d.ts
140@@ -1091,6 +1091,13 @@ export type NativeStackHeaderItemSpacing = {
141 */
142 export type NativeStackHeaderItemCustom = {
143 type: 'custom';
144+ /**
145+ * An identifier used to match items across transitions.
146+ * Only available from iOS 26.0 and later.
147+ *
148+ * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier
149+ */
150+ identifier?: string;
151 /**
152 * A React Element to display as the item.
153 */
154diff --git a/src/types.tsx b/src/types.tsx
155index 7488b1cf20afe36b5c585fbfcd369471fdb6ab8d..f4bd78e48c687024ba84e998fac79e9eb16f1a80 100644
156--- a/src/types.tsx
157+++ b/src/types.tsx
158@@ -1156,6 +1156,13 @@ export type NativeStackHeaderItemSpacing = {
159 */
160 export type NativeStackHeaderItemCustom = {
161 type: 'custom';
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
3737 override fun setTitleFontFamily(
3838 config: ScreenStackHeaderConfig,
3939 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(
4057diff --git a/ios/RNSBarButtonItem.h b/ios/RNSBarButtonItem.h
4158index ea5325ea8d17b1ddfa790ff8dab48ce83142d4d3..acd2fd7ceb7162ed300abc4d3c9f0c24f4d63898 100644
4259--- a/ios/RNSBarButtonItem.h
......@@ -140,10 +157,10 @@ index 919b984edc9f91ee9ac26faf257d8a721e26457c..5bb0cd6736ed6bc51db57e2a9326f758
140157
141158 NS_ASSUME_NONNULL_END
142159diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm
143index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb2199dcbd58 100644
160index 5970e3e3a624b9498b8dedfc16831df03a274d0c..c23776376110b6eda77072b134da29428f4bcba3 100644
144161--- a/ios/RNSScreenStackHeaderConfig.mm
145162+++ b/ios/RNSScreenStackHeaderConfig.mm
146@@ -25,11 +25,33 @@
163@@ -25,11 +25,46 @@
147164 #import "RNSSearchBar.h"
148165 #import "UINavigationBar+RNSUtility.h"
149166
......@@ -153,10 +170,23 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
153170
154171 static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17;
155172+
156+// Keys for the last-applied JS bar button configs, associated with the
157+// navigation item so unrelated header updates (title, subtitle, tint) don't
158+// recreate the native buttons they configure.
159+static char RNSAppliedHeaderBarButtonConfigsKey;
173+#if !TARGET_OS_TV
174+// Each placement keeps its own cache so changes to one side do not move live
175+// items on the other side into new groups during a navigation transition.
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
160190+static char RNSAppliedToolbarConfigsKey;
161191 static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34;
162192
......@@ -177,7 +207,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
177207 @interface RCTImageLoader (Private)
178208 - (id<RCTImageCache>)imageCache;
179209 @end
180@@ -47,6 +69,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string
210@@ -47,6 +82,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string
181211 @end
182212
183213 @interface RNSScreenStackHeaderConfig () <RCTMountingTransactionObserving>
......@@ -187,7 +217,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
187217 @end
188218
189219 @implementation RNSScreenStackHeaderConfig {
190@@ -81,6 +106,7 @@ - (void)initProps
220@@ -81,6 +119,7 @@ - (void)initProps
191221 self.hidden = YES;
192222 _reactSubviews = [NSMutableArray new];
193223 _backTitleVisible = YES;
......@@ -195,7 +225,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
195225 _blurEffect = RNSBlurEffectStyleNone;
196226 }
197227
198@@ -496,6 +522,10 @@ + (void)updateViewController:(UIViewController *)vc
228@@ -496,6 +535,10 @@ + (void)updateViewController:(UIViewController *)vc
199229
200230 if (shouldHide) {
201231 navitem.title = config.title;
......@@ -206,7 +236,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
206236
207237 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items.
208238 [navctr setNavigationBarHidden:YES animated:animated];
209@@ -512,11 +542,19 @@ + (void)updateViewController:(UIViewController *)vc
239@@ -512,11 +555,19 @@ + (void)updateViewController:(UIViewController *)vc
210240 }
211241 navitem.largeTitleDisplayMode =
212242 config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;
......@@ -226,19 +256,19 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
226256
227257 // appearance does not apply to the tvOS so we need to use lagacy customization
228258 #if TARGET_OS_TV
229@@ -550,31 +588,25 @@ + (void)updateViewController:(UIViewController *)vc
259@@ -548,8 +599,6 @@ + (void)updateViewController:(UIViewController *)vc
260 navitem.leftItemsSupplementBackButton = config.backButtonInCustomView;
261 #endif
230262 navitem.titleView = nil;
231263- navitem.leftBarButtonItems = nil;
232264- navitem.rightBarButtonItems = nil;
233-
234+
265
235266 #if !TARGET_OS_TV
236267 // 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
238269 bool searchBarPresent = false;
239270 #endif /* !TARGET_OS_TV */
240-
241+
271
242272+ NSMutableArray<UIBarButtonItem *> *subviewLeftItems = [NSMutableArray array];
243273+ NSMutableArray<UIBarButtonItem *> *subviewRightItems = [NSMutableArray array];
244274 for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
......@@ -262,9 +292,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
262292 break;
263293 }
264294 case RNSScreenStackHeaderSubviewTypeCenter:
265 case RNSScreenStackHeaderSubviewTypeTitle: {
266 navitem.titleView = subview;
267@@ -637,10 +669,467 @@ + (void)updateViewController:(UIViewController *)vc
295@@ -637,10 +682,453 @@ + (void)updateViewController:(UIViewController *)vc
268296 // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug).
269297 // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments)
270298 navitem.title = config.title;
......@@ -272,9 +300,6 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
272300- withCurrentItems:navitem.leftBarButtonItems];
273301- navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
274302- withCurrentItems:navitem.rightBarButtonItems];
275+ NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[];
276+ NSArray *headerRightConfigs = config.headerRightBarButtonItems ?: @[];
277+ NSArray *headerCenterConfigs = config.headerCenterBarButtonItems ?: @[];
278303+ // Retain the config in the key so its address cannot be reused while cached.
279304+ // Items capture this config's event emitter in their press handlers, so a
280305+ // remounted header-config view with value-equal configs must still rebuild
......@@ -283,59 +308,48 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
283308+ // subview. Include those native identities in the key so React-backed
284309+ // items can use the same cache without reusing a group after its view was
285310+ // replaced.
286+ NSArray *headerItemsKey = @[
287+ config,
288+ headerLeftConfigs,
289+ headerRightConfigs,
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;
311+ // A bar button item can belong to only one group. Even if the item itself
312+ // is reused, constructing a new group removes it from the old group that
313+ // UIKit may still be animating. Preserve each unchanged placement's group,
314+ // including the empty leading group beside the system back button.
302315+#if !TARGET_OS_TV
303316+ if (@available(iOS 16.0, *)) {
304+ canReuseHeaderBarButtonItems = YES;
305+ }
317+ NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[];
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
306343+#endif
307+ BOOL reuseHeaderBarButtonItems = canReuseHeaderBarButtonItems &&
308+ [objc_getAssociatedObject(navitem, &RNSAppliedHeaderBarButtonConfigsKey) isEqual:headerItemsKey];
309+ if (!reuseHeaderBarButtonItems) {
344+ {
310345+ NSArray<UIBarButtonItem *> *leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems
311346+ withCurrentItems:subviewLeftItems
312347+ navigationItem:navitem];
313348+ NSArray<UIBarButtonItem *> *rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
314349+ withCurrentItems:subviewRightItems
315350+ 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
331351+ navitem.leftBarButtonItems = leftBarButtonItems;
332352+ navitem.rightBarButtonItems = rightBarButtonItems;
333+#endif
334+ objc_setAssociatedObject(
335+ navitem,
336+ &RNSAppliedHeaderBarButtonConfigsKey,
337+ headerItemsKey,
338+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
339353+ }
340354+ NSDictionary<NSString *, id> *mailSearchToolbarConfig = nil;
341355+ for (NSDictionary<NSString *, id> *toolbarConfig in config.headerToolbarItems) {
......@@ -736,7 +750,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
736750
737751 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items
738752 // (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 *
740754
741755 - (NSArray<UIBarButtonItem *> *)barButtonItemsFromConfigs:(NSArray<NSDictionary<NSString *, id> *> *)dicts
742756 withCurrentItems:(NSArray<UIBarButtonItem *> *)currentItems
......@@ -744,7 +758,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
744758 {
745759 if (dicts.count == 0) {
746760 return currentItems;
747@@ -781,7 +1271,197 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
761@@ -781,7 +1270,197 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
748762 [items addObjectsFromArray:currentItems];
749763 for (NSUInteger i = 0; i < dicts.count; i++) {
750764 NSDictionary *dict = dicts[i];
......@@ -943,7 +957,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
943957 RNSBarButtonItem *item = [[RNSBarButtonItem alloc] initWithConfig:dict
944958 action:^(NSString *buttonId) {
945959 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 *
947961 }
948962 imageLoader:_imageLoader];
949963 NSNumber *index = dict[@"index"];
......@@ -973,7 +987,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
973987 [items insertObject:item atIndex:index.integerValue];
974988 } else {
975989 [items addObject:item];
976@@ -825,6 +1509,47 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
990@@ -825,6 +1508,47 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
977991 return items;
978992 }
979993
......@@ -1021,7 +1035,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
10211035 RNS_IGNORE_SUPER_CALL_BEGIN
10221036 - (void)insertReactSubview:(RNSScreenStackHeaderSubview *)subview atIndex:(NSInteger)atIndex
10231037 {
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::
10251039 }
10261040
10271041 _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title);
......@@ -1030,7 +1044,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
10301044 if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) {
10311045 _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily);
10321046 }
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::
10341048 _disableBackButtonMenu = newScreenProps.disableBackButtonMenu;
10351049 _backButtonDisplayMode =
10361050 [RNSConvert UINavigationItemBackButtonDisplayModeFromCppEquivalent:newScreenProps.backButtonDisplayMode];
......@@ -1038,7 +1052,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
10381052
10391053 if (newScreenProps.userInterfaceStyle != oldScreenProps.userInterfaceStyle) {
10401054 _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::
10421056 _headerRightBarButtonItems = array;
10431057 }
10441058
......@@ -1069,6 +1083,65 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21
10691083 [self updateViewControllerIfNeeded];
10701084
10711085 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
10721145diff --git a/lib/commonjs/components/ScreenStackHeaderConfig.js b/lib/commonjs/components/ScreenStackHeaderConfig.js
10731146index 16b979bb3dfb41ff247403f1632c300a9a60d549..01415d4cba66086a8d8e5af728f809aee5190d91 100644
10741147--- a/lib/commonjs/components/ScreenStackHeaderConfig.js
......@@ -1347,8 +1420,20 @@ index b7568ecfebd4f4420f2a8d3fec5184d7fc728dd1..41ca1f273f0175929e73105faa463978
13471420 onPressHeaderBarButtonItem?: CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent> | undefined;
13481421 onPressHeaderBarButtonMenuItem?: CT.DirectEventHandler<OnPressHeaderBarButtonMenuItemEvent> | undefined;
13491422 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>;
13501435diff --git a/lib/typescript/types.d.ts b/lib/typescript/types.d.ts
1351index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503dbe6ee9840 100644
1436index 3b384e03891e38e936f370372a682d73440e7ec2..f8ec3b46a92bf20e2845eb5867af5c372215a532 100644
13521437--- a/lib/typescript/types.d.ts
13531438+++ b/lib/typescript/types.d.ts
13541439@@ -10,6 +10,7 @@ export type SearchBarCommands = {
......@@ -1422,7 +1507,21 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db
14221507 /**
14231508 * Allows for setting text color of the title.
14241509 */
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 {
14261525 * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property
14271526 */
14281527 variant?: 'plain' | 'done' | 'prominent' | undefined;
......@@ -1434,7 +1533,7 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db
14341533 /**
14351534 * The tint color to apply to the item.
14361535 *
1437@@ -1145,8 +1193,38 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
1536@@ -1145,8 +1200,38 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
14381537 export interface HeaderBarButtonItemSpacing {
14391538 type: 'spacing';
14401539 spacing: number;
......@@ -1688,8 +1787,20 @@ index ba2479de0f49c112470f78c5084059ddd9e2f3e8..8677583a8f8daa4839340467466bfab8
16881787 onPressHeaderBarButtonItem?:
16891788 | CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent>
16901789 | 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
16911802diff --git a/src/types.tsx b/src/types.tsx
1692index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e131d21242b 100644
1803index 76a83f3acb6fd3f0af7f027798848b7124100286..39f0ae8065f4dc99a1571a3fc1cb3398caf03ff7 100644
16931804--- a/src/types.tsx
16941805+++ b/src/types.tsx
16951806@@ -26,6 +26,7 @@ export type SearchBarCommands = {
......@@ -1763,7 +1874,21 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13
17631874 /**
17641875 * Allows for setting text color of the title.
17651876 */
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 {
17671892 * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property
17681893 */
17691894 variant?: 'plain' | 'done' | 'prominent' | undefined;
......@@ -1775,7 +1900,7 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13
17751900 /**
17761901 * The tint color to apply to the item.
17771902 *
1778@@ -1279,11 +1327,47 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
1903@@ -1279,11 +1334,47 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
17791904 export interface HeaderBarButtonItemSpacing {
17801905 type: 'spacing';
17811906 spacing: number;
pnpm-lock.yaml+16-16
......@@ -88,13 +88,13 @@ patchedDependencies:
8888 '@pierre/diffs@1.3.0-beta.10': 7ef7cb0cbabb17c15cdb137554068b36f15f6f5265e73fb51452aa3380db91aa
8989 '@react-native-menu/menu@2.0.0': c7f66d121c726ade4f5c4e1aed11a691e5711d244c544084e289ac26132a0045
9090 '@react-native/gradle-plugin@0.85.3': c1b594a16e682d621b6a960926f8ce13fc92edfb397884cfe7fcb0518996a784
91 '@react-navigation/native-stack@7.17.6': 0365b727005b3a830af80ccbd0b637666cc0338d33ddcb7a25b6de51a21ea027
91 '@react-navigation/native-stack@7.17.6': e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552
9292 effect@4.0.0-beta.103: af36b7948b6f9c56623074662b51dade5699880c1a7c71245de73e13c3185fb6
9393 expo-modules-jsi@56.0.10: 9170f8074ae4e35a0a086e756c8f815794fd3abe51eac67ca3ba02804225ec1f
9494 react-native-gesture-handler@2.31.2: 808eb26f9e57cf4945efd3985af4d9c764da6f91f4c9764433cc868602bbf4d3
9595 react-native-keyboard-controller@1.21.13: 20be72c84d74253acdcfefbc6defe36dc396944f1a44cab2bdd0e3cd572ae008
9696 react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675
97 react-native-screens@4.25.2: b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc
97 react-native-screens@4.25.2: 7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e
9898
9999importers:
100100
......@@ -248,7 +248,7 @@ importers:
248248 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)
249249 '@react-navigation/native-stack':
250250 specifier: 7.17.6
251 version: 7.17.6(patch_hash=0365b727005b3a830af80ccbd0b637666cc0338d33ddcb7a25b6de51a21ea027)(e49ba4adb65ddb2cde0c8b8b274b705b)
251 version: 7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(8effe605a545f8a17acafbaed58f0e86)
252252 '@shikijs/core':
253253 specifier: 4.2.0
254254 version: 4.2.0
......@@ -416,7 +416,7 @@ importers:
416416 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)
417417 react-native-screens:
418418 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)
420420 react-native-shiki-engine:
421421 specifier: ^0.3.12
422422 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:
1225212252 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1225312253 zod: 3.25.76
1225412254 optionalDependencies:
12255 expo-router: 56.2.11(b91ff5333bc046aa08fb7484e4e065d6)
12255 expo-router: 56.2.11(1d12f8130d9562de288829ccb10045a7)
1225612256 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)
1225712257 transitivePeerDependencies:
1225812258 - '@expo/dom-webview'
......@@ -12328,7 +12328,7 @@ snapshots:
1232812328 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1232912329 zod: 3.25.76
1233012330 optionalDependencies:
12331 expo-router: 56.2.11(f8b3939b2a15129a8b59814542a58c1b)
12331 expo-router: 56.2.11(5cef6fdc582974f4d93d5694873ec814)
1233212332 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)
1233312333 transitivePeerDependencies:
1233412334 - '@expo/dom-webview'
......@@ -12668,7 +12668,7 @@ snapshots:
1266812668 react: 19.2.3
1266912669 optionalDependencies:
1267012670 '@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)
1267212672 react-dom: 19.2.3(react@19.2.3)
1267312673 transitivePeerDependencies:
1267412674 - supports-color
......@@ -12683,7 +12683,7 @@ snapshots:
1268312683 react: 19.2.6
1268412684 optionalDependencies:
1268512685 '@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)
1268712687 react-dom: 19.2.6(react@19.2.6)
1268812688 transitivePeerDependencies:
1268912689 - supports-color
......@@ -14268,7 +14268,7 @@ snapshots:
1426814268 optionalDependencies:
1426914269 '@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)':
1427214272 dependencies:
1427314273 '@react-navigation/elements': 2.9.26(c6a2ad0e2c930f8e3896e77c3ba11bc4)
1427414274 '@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:
1427614276 react: 19.2.3
1427714277 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)
1427814278 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)
1428014280 sf-symbols-typescript: 2.2.0
1428114281 warn-once: 0.1.1
1428214282 transitivePeerDependencies:
......@@ -17062,7 +17062,7 @@ snapshots:
1706217062 - supports-color
1706317063 - typescript
1706417064
17065 expo-router@56.2.11(b91ff5333bc046aa08fb7484e4e065d6):
17065 expo-router@56.2.11(1d12f8130d9562de288829ccb10045a7):
1706617066 dependencies:
1706717067 '@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)
1706817068 '@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:
1709317093 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)
1709417094 react-native-drawer-layout: 4.2.4(05364bd849de538917a7364cc7dee3f5)
1709517095 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)
1709717097 server-only: 0.0.1
1709817098 sf-symbols-typescript: 2.2.0
1709917099 shallowequal: 1.1.0
......@@ -17113,7 +17113,7 @@ snapshots:
1711317113 - supports-color
1711417114 optional: true
1711517115
17116 expo-router@56.2.11(f8b3939b2a15129a8b59814542a58c1b):
17116 expo-router@56.2.11(5cef6fdc582974f4d93d5694873ec814):
1711717117 dependencies:
1711817118 '@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)
1711917119 '@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:
1714417144 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)
1714517145 react-native-drawer-layout: 4.2.4(de9b2f2dc96a3557fdc0df187a8417ee)
1714617146 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)
1714817148 server-only: 0.0.1
1714917149 sf-symbols-typescript: 2.2.0
1715017150 shallowequal: 1.1.0
......@@ -19959,14 +19959,14 @@ snapshots:
1995919959 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)
1996019960 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):
1996319963 dependencies:
1996419964 react: 19.2.3
1996519965 react-freeze: 1.0.4(react@19.2.3)
1996619966 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)
1996719967 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):
1997019970 dependencies:
1997119971 react: 19.2.6
1997219972 react-freeze: 1.0.4(react@19.2.6)