| author | |
| committer | |
| log | 4669eab8e0e01d07141a2cb49ca21a9e15ae1429 |
| tree | 5e8093096bdac110520e09ee0dd2f8ed8872692e |
| parent | 702a6ade3c69bf1c9ce06ad0451dbd8e0ecc7be9 |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | 15 | const IOS_NATIVE_LEADING_TITLE_OFFSET = -6; |
| 16 | 16 | const IPAD_NATIVE_LEADING_TITLE_OFFSET = 7; |
| 17 | 17 | |
| 18 | // Branding and connection status share an identity distinct from navigation buttons. | |
| 19 | export const BRAND_HEADER_ITEM_IDENTIFIER = "workspace-brand"; | |
| 20 | ||
| 18 | 21 | /** |
| 19 | 22 | * Horizontal correction applied to content rendered in the brand title slot, |
| 20 | 23 | * shared with the connection-status swap so both align identically. |
| ... | ... | @@ -76,6 +79,7 @@ export function renderCompactBrandHeaderItems(): NativeStackHeaderItem[] { |
| 76 | 79 | { |
| 77 | 80 | element: <CompactBrandTitle nativeLeadingItem />, |
| 78 | 81 | hidesSharedBackground: true, |
| 82 | identifier: BRAND_HEADER_ITEM_IDENTIFIER, | |
| 79 | 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 | |
| 8 | 8 | import { SymbolView } from "../../components/AppSymbol"; |
| 9 | 9 | import { AppText as Text } from "../../components/AppText"; |
| 10 | import { brandTitleOffset, CompactBrandTitle } from "../../components/CompactBrandTitle"; | |
| 10 | import { | |
| 11 | BRAND_HEADER_ITEM_IDENTIFIER, | |
| 12 | brandTitleOffset, | |
| 13 | CompactBrandTitle, | |
| 14 | } from "../../components/CompactBrandTitle"; | |
| 11 | 15 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| 12 | 16 | import { useWorkspaceState } from "../../state/workspace"; |
| 13 | 17 | import { |
| ... | ... | @@ -172,6 +176,7 @@ export function getConnectionAwareBrandHeaderOptions(opts: { |
| 172 | 176 | /> |
| 173 | 177 | ), |
| 174 | 178 | hidesSharedBackground: true, |
| 179 | identifier: BRAND_HEADER_ITEM_IDENTIFIER, | |
| 175 | 180 | type: "custom", |
| 176 | 181 | }, |
| 177 | 182 | ], |
docs/internals/mobile-navigation.md created+31| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | # Mobile navigation headers | |
| 2 | ||
| 3 | The iOS Home and thread routes share the root native stack in | |
| 4 | [`Stack.tsx`](../../apps/mobile/src/Stack.tsx). Keeping them in one navigation | |
| 5 | controller lets UIKit animate the header between routes. The iPad sidebar owns | |
| 6 | a separate, single-screen stack; Android uses its own in-flow headers. | |
| 7 | ||
| 8 | On Liquid Glass iOS, Home keeps a transparent native title and renders its brand | |
| 9 | as a custom leading item. A custom `headerTitle` previously shortened the scroll | |
| 10 | fade and removed the blur behind the logo. Preserve the native title when | |
| 11 | changing the brand or its connection-status replacement. | |
| 12 | ||
| 13 | Both brand states use `BRAND_HEADER_ITEM_IDENTIFIER` from | |
| 14 | [`CompactBrandTitle.tsx`](../../apps/mobile/src/components/CompactBrandTitle.tsx). | |
| 15 | The native-stack and react-native-screens patches forward custom item identifiers | |
| 16 | to `UIBarButtonItem.identifier`, just as they do for native buttons and menus. | |
| 17 | UIKit otherwise matches transition items using their position and content, so | |
| 18 | the brand needs an explicit identity separate from navigation controls. Connection | |
| 19 | status changes retain that identity. See [Apple's identifier documentation](https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier). | |
| 20 | ||
| 21 | The react-native-screens patch caches leading, trailing, and center item groups | |
| 22 | independently. A button can belong to only one group: constructing another group | |
| 23 | with the same button removes it from the previous one. Unrelated menu updates | |
| 24 | must therefore preserve the other groups while UIKit may be animating them. | |
| 25 | Each cache includes the owning header config, its item values, and custom native | |
| 26 | item identities, so changed content or remounted event emitters still rebuild. | |
| 27 | ||
| 28 | Custom header identifiers require native code generation and a new mobile build; | |
| 29 | an over-the-air JavaScript update alone is insufficient. The Android view manager | |
| 30 | implements the generated identifier setter as a no-op because this behavior is | |
| 31 | specific to iOS 26 and later. |
docs/internals/overview.md+1| ... | ... | @@ -132,6 +132,7 @@ already dispatch. |
| 132 | 132 | ## Related |
| 133 | 133 | |
| 134 | 134 | - [Workspace layout](./workspace-layout.md), [Glossary](./glossary.md) |
| 135 | - [Mobile navigation headers](./mobile-navigation.md) | |
| 135 | 136 | - [Remote environments](./remote.md), [Server updates](./server-updates.md) |
| 136 | 137 | - [Resource telemetry](./resource-telemetry.md) |
| 137 | 138 | - [Product analytics](./product-analytics.md) |
patches/@react-navigation%2Fnative-stack@7.17.6.patch+103-36| ... | ... | @@ -1,8 +1,30 @@ |
| 1 | diff --git a/lib/module/views/NativeStackView.native.js b/lib/module/views/NativeStackView.native.js | |
| 2 | index 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; | |
| 1 | 23 | diff --git a/lib/module/views/useHeaderConfigProps.js b/lib/module/views/useHeaderConfigProps.js |
| 2 | index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b01175ec19220 100644 | |
| 24 | index 9c21aa48cb580704aa17e59d9cdadcc612a1e4ff..a1b3a38368c5ef2257570f382e87df5d6f55b11e 100644 | |
| 3 | 25 | --- a/lib/module/views/useHeaderConfigProps.js |
| 4 | 26 | +++ b/lib/module/views/useHeaderConfigProps.js |
| 5 | @@ -19,6 +19,12 @@ | |
| 27 | @@ -19,6 +19,12 @@ const processBarButtonItems = (items, colors, fonts) => { | |
| 6 | 28 | } |
| 7 | 29 | return item; |
| 8 | 30 | } |
| ... | ... | @@ -15,7 +37,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 15 | 37 | if (item.type === 'button' || item.type === 'menu') { |
| 16 | 38 | if (item.type === 'menu' && item.menu == null) { |
| 17 | 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 | 42 | return processedItem; |
| 21 | 43 | } |
| ... | ... | @@ -24,7 +46,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 24 | 46 | }).filter(item => item != null); |
| 25 | 47 | }; |
| 26 | 48 | const transformIcon = icon => { |
| 27 | @@ -158,8 +164,12 @@ | |
| 49 | @@ -158,8 +164,12 @@ export function useHeaderConfigProps({ | |
| 28 | 50 | headerBack, |
| 29 | 51 | route, |
| 30 | 52 | title, |
| ... | ... | @@ -38,7 +60,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 38 | 60 | }) { |
| 39 | 61 | const { |
| 40 | 62 | direction |
| 41 | @@ -255,6 +265,10 @@ | |
| 63 | @@ -255,6 +265,10 @@ export function useHeaderConfigProps({ | |
| 42 | 64 | tintColor, |
| 43 | 65 | canGoBack |
| 44 | 66 | }); |
| ... | ... | @@ -49,7 +71,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 49 | 71 | let rightItems = headerRightItems?.({ |
| 50 | 72 | tintColor, |
| 51 | 73 | canGoBack |
| 52 | @@ -264,6 +278,10 @@ | |
| 74 | @@ -264,6 +278,10 @@ export function useHeaderConfigProps({ | |
| 53 | 75 | // So we need to reverse them here to match the order |
| 54 | 76 | rightItems = [...rightItems].reverse(); |
| 55 | 77 | } |
| ... | ... | @@ -60,7 +82,14 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 60 | 82 | const children = /*#__PURE__*/_jsxs(_Fragment, { |
| 61 | 83 | children: [Platform.OS === 'ios' ? /*#__PURE__*/_jsxs(_Fragment, { |
| 62 | 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 | 93 | return null; |
| 65 | 94 | }) : headerLeftElement != null ? /*#__PURE__*/_jsx(ScreenStackHeaderLeftView, { |
| 66 | 95 | children: headerLeftElement |
| ... | ... | @@ -69,6 +98,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 69 | 98 | + if (item.type === 'custom') { |
| 70 | 99 | + return /*#__PURE__*/_jsx(ScreenStackHeaderCenterView, { |
| 71 | 100 | + hidesSharedBackground: item.hidesSharedBackground, |
| 101 | + identifier: item.identifier, | |
| 72 | 102 | + children: item.element |
| 73 | 103 | + }, index); |
| 74 | 104 | + } |
| ... | ... | @@ -77,7 +107,15 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 77 | 107 | children: headerTitleElement |
| 78 | 108 | }) : null] |
| 79 | 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 | 119 | largeTitleFontWeight, |
| 82 | 120 | largeTitleHideShadow: headerLargeTitleShadowVisible === false, |
| 83 | 121 | title: titleText, |
| ... | ... | @@ -85,7 +123,7 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 85 | 123 | titleColor, |
| 86 | 124 | titleFontFamily, |
| 87 | 125 | titleFontSize, |
| 88 | @@ -364,9 +391,12 @@ | |
| 126 | @@ -364,6 +394,9 @@ export function useHeaderConfigProps({ | |
| 89 | 127 | disableTopInsetApplication: !headerTopInsetEnabled, |
| 90 | 128 | translucent: translucent === true, |
| 91 | 129 | children, |
| ... | ... | @@ -95,30 +133,59 @@ index 0b75c70b4e0d233ee3b5faaf9cfbc40d4f8ed494..eb174e3fde91a7783f132b3fb16b0117 |
| 95 | 133 | headerLeftBarButtonItems: processBarButtonItems(leftItems, colors, fonts), |
| 96 | 134 | headerRightBarButtonItems: processBarButtonItems(rightItems, colors, fonts), |
| 97 | 135 | experimental_userInterfaceStyle: dark ? 'dark' : 'light' |
| 98 | }; | |
| 99 | } | |
| 100 | -//# sourceMappingURL=useHeaderConfigProps.js.map | |
| 101 | \ No newline at end of file | |
| 102 | +//# sourceMappingURL=useHeaderConfigProps.js.map | |
| 103 | diff --git a/lib/module/views/NativeStackView.native.js b/lib/module/views/NativeStackView.native.js | |
| 104 | index 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; | |
| 136 | diff --git a/lib/typescript/src/types.d.ts b/lib/typescript/src/types.d.ts | |
| 137 | index 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 | */ | |
| 154 | diff --git a/src/types.tsx b/src/types.tsx | |
| 155 | index 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 | */ | |
| 172 | diff --git a/src/views/useHeaderConfigProps.tsx b/src/views/useHeaderConfigProps.tsx | |
| 173 | index 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 | 37 | override fun setTitleFontFamily( |
| 38 | 38 | config: ScreenStackHeaderConfig, |
| 39 | 39 | titleFontFamily: String?, |
| 40 | diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt | |
| 41 | index 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( | |
| 40 | 57 | diff --git a/ios/RNSBarButtonItem.h b/ios/RNSBarButtonItem.h |
| 41 | 58 | index ea5325ea8d17b1ddfa790ff8dab48ce83142d4d3..acd2fd7ceb7162ed300abc4d3c9f0c24f4d63898 100644 |
| 42 | 59 | --- a/ios/RNSBarButtonItem.h |
| ... | ... | @@ -140,10 +157,10 @@ index 919b984edc9f91ee9ac26faf257d8a721e26457c..5bb0cd6736ed6bc51db57e2a9326f758 |
| 140 | 157 | |
| 141 | 158 | NS_ASSUME_NONNULL_END |
| 142 | 159 | diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm |
| 143 | index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb2199dcbd58 100644 | |
| 160 | index 5970e3e3a624b9498b8dedfc16831df03a274d0c..c23776376110b6eda77072b134da29428f4bcba3 100644 | |
| 144 | 161 | --- a/ios/RNSScreenStackHeaderConfig.mm |
| 145 | 162 | +++ b/ios/RNSScreenStackHeaderConfig.mm |
| 146 | @@ -25,11 +25,33 @@ | |
| 163 | @@ -25,11 +25,46 @@ | |
| 147 | 164 | #import "RNSSearchBar.h" |
| 148 | 165 | #import "UINavigationBar+RNSUtility.h" |
| 149 | 166 | |
| ... | ... | @@ -153,10 +170,23 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 153 | 170 | |
| 154 | 171 | static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17; |
| 155 | 172 | + |
| 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 | |
| 160 | 190 | +static char RNSAppliedToolbarConfigsKey; |
| 161 | 191 | static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; |
| 162 | 192 | |
| ... | ... | @@ -177,7 +207,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 177 | 207 | @interface RCTImageLoader (Private) |
| 178 | 208 | - (id<RCTImageCache>)imageCache; |
| 179 | 209 | @end |
| 180 | @@ -47,6 +69,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string | |
| 210 | @@ -47,6 +82,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string | |
| 181 | 211 | @end |
| 182 | 212 | |
| 183 | 213 | @interface RNSScreenStackHeaderConfig () <RCTMountingTransactionObserving> |
| ... | ... | @@ -187,7 +217,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 187 | 217 | @end |
| 188 | 218 | |
| 189 | 219 | @implementation RNSScreenStackHeaderConfig { |
| 190 | @@ -81,6 +106,7 @@ - (void)initProps | |
| 220 | @@ -81,6 +119,7 @@ - (void)initProps | |
| 191 | 221 | self.hidden = YES; |
| 192 | 222 | _reactSubviews = [NSMutableArray new]; |
| 193 | 223 | _backTitleVisible = YES; |
| ... | ... | @@ -195,7 +225,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 195 | 225 | _blurEffect = RNSBlurEffectStyleNone; |
| 196 | 226 | } |
| 197 | 227 | |
| 198 | @@ -496,6 +522,10 @@ + (void)updateViewController:(UIViewController *)vc | |
| 228 | @@ -496,6 +535,10 @@ + (void)updateViewController:(UIViewController *)vc | |
| 199 | 229 | |
| 200 | 230 | if (shouldHide) { |
| 201 | 231 | navitem.title = config.title; |
| ... | ... | @@ -206,7 +236,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 206 | 236 | |
| 207 | 237 | // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items. |
| 208 | 238 | [navctr setNavigationBarHidden:YES animated:animated]; |
| 209 | @@ -512,11 +542,19 @@ + (void)updateViewController:(UIViewController *)vc | |
| 239 | @@ -512,11 +555,19 @@ + (void)updateViewController:(UIViewController *)vc | |
| 210 | 240 | } |
| 211 | 241 | navitem.largeTitleDisplayMode = |
| 212 | 242 | config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever; |
| ... | ... | @@ -226,19 +256,19 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 226 | 256 | |
| 227 | 257 | // appearance does not apply to the tvOS so we need to use lagacy customization |
| 228 | 258 | #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 | |
| 230 | 262 | navitem.titleView = nil; |
| 231 | 263 | - navitem.leftBarButtonItems = nil; |
| 232 | 264 | - navitem.rightBarButtonItems = nil; |
| 233 | - | |
| 234 | + | |
| 265 | ||
| 235 | 266 | #if !TARGET_OS_TV |
| 236 | 267 | // 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 | 269 | bool searchBarPresent = false; |
| 239 | 270 | #endif /* !TARGET_OS_TV */ |
| 240 | - | |
| 241 | + | |
| 271 | ||
| 242 | 272 | + NSMutableArray<UIBarButtonItem *> *subviewLeftItems = [NSMutableArray array]; |
| 243 | 273 | + NSMutableArray<UIBarButtonItem *> *subviewRightItems = [NSMutableArray array]; |
| 244 | 274 | for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) { |
| ... | ... | @@ -262,9 +292,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 262 | 292 | break; |
| 263 | 293 | } |
| 264 | 294 | 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 | |
| 268 | 296 | // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). |
| 269 | 297 | // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) |
| 270 | 298 | navitem.title = config.title; |
| ... | ... | @@ -272,9 +300,6 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 272 | 300 | - withCurrentItems:navitem.leftBarButtonItems]; |
| 273 | 301 | - navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems |
| 274 | 302 | - withCurrentItems:navitem.rightBarButtonItems]; |
| 275 | + NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[]; | |
| 276 | + NSArray *headerRightConfigs = config.headerRightBarButtonItems ?: @[]; | |
| 277 | + NSArray *headerCenterConfigs = config.headerCenterBarButtonItems ?: @[]; | |
| 278 | 303 | + // Retain the config in the key so its address cannot be reused while cached. |
| 279 | 304 | + // Items capture this config's event emitter in their press handlers, so a |
| 280 | 305 | + // remounted header-config view with value-equal configs must still rebuild |
| ... | ... | @@ -283,59 +308,48 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 283 | 308 | + // subview. Include those native identities in the key so React-backed |
| 284 | 309 | + // items can use the same cache without reusing a group after its view was |
| 285 | 310 | + // 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. | |
| 302 | 315 | +#if !TARGET_OS_TV |
| 303 | 316 | + 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 | |
| 306 | 343 | +#endif |
| 307 | + BOOL reuseHeaderBarButtonItems = canReuseHeaderBarButtonItems && | |
| 308 | + [objc_getAssociatedObject(navitem, &RNSAppliedHeaderBarButtonConfigsKey) isEqual:headerItemsKey]; | |
| 309 | + if (!reuseHeaderBarButtonItems) { | |
| 344 | + { | |
| 310 | 345 | + NSArray<UIBarButtonItem *> *leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems |
| 311 | 346 | + withCurrentItems:subviewLeftItems |
| 312 | 347 | + navigationItem:navitem]; |
| 313 | 348 | + NSArray<UIBarButtonItem *> *rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems |
| 314 | 349 | + withCurrentItems:subviewRightItems |
| 315 | 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 | 351 | + navitem.leftBarButtonItems = leftBarButtonItems; |
| 332 | 352 | + navitem.rightBarButtonItems = rightBarButtonItems; |
| 333 | +#endif | |
| 334 | + objc_setAssociatedObject( | |
| 335 | + navitem, | |
| 336 | + &RNSAppliedHeaderBarButtonConfigsKey, | |
| 337 | + headerItemsKey, | |
| 338 | + OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| 339 | 353 | + } |
| 340 | 354 | + NSDictionary<NSString *, id> *mailSearchToolbarConfig = nil; |
| 341 | 355 | + for (NSDictionary<NSString *, id> *toolbarConfig in config.headerToolbarItems) { |
| ... | ... | @@ -736,7 +750,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 736 | 750 | |
| 737 | 751 | // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items |
| 738 | 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 | 755 | - (NSArray<UIBarButtonItem *> *)barButtonItemsFromConfigs:(NSArray<NSDictionary<NSString *, id> *> *)dicts |
| 742 | 756 | withCurrentItems:(NSArray<UIBarButtonItem *> *)currentItems |
| ... | ... | @@ -744,7 +758,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 744 | 758 | { |
| 745 | 759 | if (dicts.count == 0) { |
| 746 | 760 | return currentItems; |
| 747 | @@ -781,7 +1271,197 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController * | |
| 761 | @@ -781,7 +1270,197 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController * | |
| 748 | 762 | [items addObjectsFromArray:currentItems]; |
| 749 | 763 | for (NSUInteger i = 0; i < dicts.count; i++) { |
| 750 | 764 | NSDictionary *dict = dicts[i]; |
| ... | ... | @@ -943,7 +957,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 943 | 957 | RNSBarButtonItem *item = [[RNSBarButtonItem alloc] initWithConfig:dict |
| 944 | 958 | action:^(NSString *buttonId) { |
| 945 | 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 | 962 | imageLoader:_imageLoader]; |
| 949 | 963 | NSNumber *index = dict[@"index"]; |
| ... | ... | @@ -973,7 +987,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 973 | 987 | [items insertObject:item atIndex:index.integerValue]; |
| 974 | 988 | } else { |
| 975 | 989 | [items addObject:item]; |
| 976 | @@ -825,6 +1509,47 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController * | |
| 990 | @@ -825,6 +1508,47 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController * | |
| 977 | 991 | return items; |
| 978 | 992 | } |
| 979 | 993 | |
| ... | ... | @@ -1021,7 +1035,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 1021 | 1035 | RNS_IGNORE_SUPER_CALL_BEGIN |
| 1022 | 1036 | - (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 | 1041 | _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title); |
| ... | ... | @@ -1030,7 +1044,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 1030 | 1044 | if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) { |
| 1031 | 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 | 1048 | _disableBackButtonMenu = newScreenProps.disableBackButtonMenu; |
| 1035 | 1049 | _backButtonDisplayMode = |
| 1036 | 1050 | [RNSConvert UINavigationItemBackButtonDisplayModeFromCppEquivalent:newScreenProps.backButtonDisplayMode]; |
| ... | ... | @@ -1038,7 +1052,7 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 1038 | 1052 | |
| 1039 | 1053 | if (newScreenProps.userInterfaceStyle != oldScreenProps.userInterfaceStyle) { |
| 1040 | 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 | 1056 | _headerRightBarButtonItems = array; |
| 1043 | 1057 | } |
| 1044 | 1058 | |
| ... | ... | @@ -1069,6 +1083,65 @@ index 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb21 |
| 1069 | 1083 | [self updateViewControllerIfNeeded]; |
| 1070 | 1084 | |
| 1071 | 1085 | if (needsNavigationControllerLayout) { |
| 1086 | diff --git a/ios/RNSScreenStackHeaderSubview.mm b/ios/RNSScreenStackHeaderSubview.mm | |
| 1087 | index 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 | |
| 1072 | 1145 | diff --git a/lib/commonjs/components/ScreenStackHeaderConfig.js b/lib/commonjs/components/ScreenStackHeaderConfig.js |
| 1073 | 1146 | index 16b979bb3dfb41ff247403f1632c300a9a60d549..01415d4cba66086a8d8e5af728f809aee5190d91 100644 |
| 1074 | 1147 | --- a/lib/commonjs/components/ScreenStackHeaderConfig.js |
| ... | ... | @@ -1347,8 +1420,20 @@ index b7568ecfebd4f4420f2a8d3fec5184d7fc728dd1..41ca1f273f0175929e73105faa463978 |
| 1347 | 1420 | onPressHeaderBarButtonItem?: CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent> | undefined; |
| 1348 | 1421 | onPressHeaderBarButtonMenuItem?: CT.DirectEventHandler<OnPressHeaderBarButtonMenuItemEvent> | undefined; |
| 1349 | 1422 | synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, false>; |
| 1423 | diff --git a/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts b/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts | |
| 1424 | index 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>; | |
| 1350 | 1435 | diff --git a/lib/typescript/types.d.ts b/lib/typescript/types.d.ts |
| 1351 | index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503dbe6ee9840 100644 | |
| 1436 | index 3b384e03891e38e936f370372a682d73440e7ec2..f8ec3b46a92bf20e2845eb5867af5c372215a532 100644 | |
| 1352 | 1437 | --- a/lib/typescript/types.d.ts |
| 1353 | 1438 | +++ b/lib/typescript/types.d.ts |
| 1354 | 1439 | @@ -10,6 +10,7 @@ export type SearchBarCommands = { |
| ... | ... | @@ -1422,7 +1507,21 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db |
| 1422 | 1507 | /** |
| 1423 | 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 | 1525 | * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property |
| 1427 | 1526 | */ |
| 1428 | 1527 | variant?: 'plain' | 'done' | 'prominent' | undefined; |
| ... | ... | @@ -1434,7 +1533,7 @@ index 3b384e03891e38e936f370372a682d73440e7ec2..861ffed850e90f27916c427853b503db |
| 1434 | 1533 | /** |
| 1435 | 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 | 1537 | export interface HeaderBarButtonItemSpacing { |
| 1439 | 1538 | type: 'spacing'; |
| 1440 | 1539 | spacing: number; |
| ... | ... | @@ -1688,8 +1787,20 @@ index ba2479de0f49c112470f78c5084059ddd9e2f3e8..8677583a8f8daa4839340467466bfab8 |
| 1688 | 1787 | onPressHeaderBarButtonItem?: |
| 1689 | 1788 | | CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent> |
| 1690 | 1789 | | undefined; |
| 1790 | diff --git a/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts b/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts | |
| 1791 | index 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 | ||
| 1691 | 1802 | diff --git a/src/types.tsx b/src/types.tsx |
| 1692 | index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e131d21242b 100644 | |
| 1803 | index 76a83f3acb6fd3f0af7f027798848b7124100286..39f0ae8065f4dc99a1571a3fc1cb3398caf03ff7 100644 | |
| 1693 | 1804 | --- a/src/types.tsx |
| 1694 | 1805 | +++ b/src/types.tsx |
| 1695 | 1806 | @@ -26,6 +26,7 @@ export type SearchBarCommands = { |
| ... | ... | @@ -1763,7 +1874,21 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13 |
| 1763 | 1874 | /** |
| 1764 | 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 | 1892 | * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property |
| 1768 | 1893 | */ |
| 1769 | 1894 | variant?: 'plain' | 'done' | 'prominent' | undefined; |
| ... | ... | @@ -1775,7 +1900,7 @@ index 76a83f3acb6fd3f0af7f027798848b7124100286..9e4499f076f9988e3266df4be7201e13 |
| 1775 | 1900 | /** |
| 1776 | 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 | 1904 | export interface HeaderBarButtonItemSpacing { |
| 1780 | 1905 | type: 'spacing'; |
| 1781 | 1906 | spacing: number; |
pnpm-lock.yaml+16-16| ... | ... | @@ -88,13 +88,13 @@ patchedDependencies: |
| 88 | 88 | '@pierre/diffs@1.3.0-beta.10': 7ef7cb0cbabb17c15cdb137554068b36f15f6f5265e73fb51452aa3380db91aa |
| 89 | 89 | '@react-native-menu/menu@2.0.0': c7f66d121c726ade4f5c4e1aed11a691e5711d244c544084e289ac26132a0045 |
| 90 | 90 | '@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 | |
| 92 | 92 | effect@4.0.0-beta.103: af36b7948b6f9c56623074662b51dade5699880c1a7c71245de73e13c3185fb6 |
| 93 | 93 | expo-modules-jsi@56.0.10: 9170f8074ae4e35a0a086e756c8f815794fd3abe51eac67ca3ba02804225ec1f |
| 94 | 94 | react-native-gesture-handler@2.31.2: 808eb26f9e57cf4945efd3985af4d9c764da6f91f4c9764433cc868602bbf4d3 |
| 95 | 95 | react-native-keyboard-controller@1.21.13: 20be72c84d74253acdcfefbc6defe36dc396944f1a44cab2bdd0e3cd572ae008 |
| 96 | 96 | react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675 |
| 97 | react-native-screens@4.25.2: b3d86ac8bf77b40f4112672f92225853bce6faca485a4aeb609a07a70162a5cc | |
| 97 | react-native-screens@4.25.2: 7474c207dfb4c474b90901a53afa13a8c869a185518d6fbe907686dc4439056e | |
| 98 | 98 | |
| 99 | 99 | importers: |
| 100 | 100 | |
| ... | ... | @@ -248,7 +248,7 @@ importers: |
| 248 | 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 | 249 | '@react-navigation/native-stack': |
| 250 | 250 | specifier: 7.17.6 |
| 251 | version: 7.17.6(patch_hash=0365b727005b3a830af80ccbd0b637666cc0338d33ddcb7a25b6de51a21ea027)(e49ba4adb65ddb2cde0c8b8b274b705b) | |
| 251 | version: 7.17.6(patch_hash=e667c3cef8c78bb9ff4882ee5bd23a432247b843060a9499eb5f07e9e2295552)(8effe605a545f8a17acafbaed58f0e86) | |
| 252 | 252 | '@shikijs/core': |
| 253 | 253 | specifier: 4.2.0 |
| 254 | 254 | version: 4.2.0 |
| ... | ... | @@ -416,7 +416,7 @@ importers: |
| 416 | 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 | 417 | react-native-screens: |
| 418 | 418 | 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 | 420 | react-native-shiki-engine: |
| 421 | 421 | specifier: ^0.3.12 |
| 422 | 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 | 12252 | ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 12253 | 12253 | zod: 3.25.76 |
| 12254 | 12254 | optionalDependencies: |
| 12255 | expo-router: 56.2.11(b91ff5333bc046aa08fb7484e4e065d6) | |
| 12255 | expo-router: 56.2.11(1d12f8130d9562de288829ccb10045a7) | |
| 12256 | 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 | 12257 | transitivePeerDependencies: |
| 12258 | 12258 | - '@expo/dom-webview' |
| ... | ... | @@ -12328,7 +12328,7 @@ snapshots: |
| 12328 | 12328 | ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 12329 | 12329 | zod: 3.25.76 |
| 12330 | 12330 | optionalDependencies: |
| 12331 | expo-router: 56.2.11(f8b3939b2a15129a8b59814542a58c1b) | |
| 12331 | expo-router: 56.2.11(5cef6fdc582974f4d93d5694873ec814) | |
| 12332 | 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 | 12333 | transitivePeerDependencies: |
| 12334 | 12334 | - '@expo/dom-webview' |
| ... | ... | @@ -12668,7 +12668,7 @@ snapshots: |
| 12668 | 12668 | react: 19.2.3 |
| 12669 | 12669 | optionalDependencies: |
| 12670 | 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 | 12672 | react-dom: 19.2.3(react@19.2.3) |
| 12673 | 12673 | transitivePeerDependencies: |
| 12674 | 12674 | - supports-color |
| ... | ... | @@ -12683,7 +12683,7 @@ snapshots: |
| 12683 | 12683 | react: 19.2.6 |
| 12684 | 12684 | optionalDependencies: |
| 12685 | 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 | 12687 | react-dom: 19.2.6(react@19.2.6) |
| 12688 | 12688 | transitivePeerDependencies: |
| 12689 | 12689 | - supports-color |
| ... | ... | @@ -14268,7 +14268,7 @@ snapshots: |
| 14268 | 14268 | optionalDependencies: |
| 14269 | 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) |
| 14270 | 14270 | |
| 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 | 14272 | dependencies: |
| 14273 | 14273 | '@react-navigation/elements': 2.9.26(c6a2ad0e2c930f8e3896e77c3ba11bc4) |
| 14274 | 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 | 14276 | react: 19.2.3 |
| 14277 | 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 | 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 | 14280 | sf-symbols-typescript: 2.2.0 |
| 14281 | 14281 | warn-once: 0.1.1 |
| 14282 | 14282 | transitivePeerDependencies: |
| ... | ... | @@ -17062,7 +17062,7 @@ snapshots: |
| 17062 | 17062 | - supports-color |
| 17063 | 17063 | - typescript |
| 17064 | 17064 | |
| 17065 | expo-router@56.2.11(b91ff5333bc046aa08fb7484e4e065d6): | |
| 17065 | expo-router@56.2.11(1d12f8130d9562de288829ccb10045a7): | |
| 17066 | 17066 | dependencies: |
| 17067 | 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 | 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 | 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 | 17094 | react-native-drawer-layout: 4.2.4(05364bd849de538917a7364cc7dee3f5) |
| 17095 | 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 | 17097 | server-only: 0.0.1 |
| 17098 | 17098 | sf-symbols-typescript: 2.2.0 |
| 17099 | 17099 | shallowequal: 1.1.0 |
| ... | ... | @@ -17113,7 +17113,7 @@ snapshots: |
| 17113 | 17113 | - supports-color |
| 17114 | 17114 | optional: true |
| 17115 | 17115 | |
| 17116 | expo-router@56.2.11(f8b3939b2a15129a8b59814542a58c1b): | |
| 17116 | expo-router@56.2.11(5cef6fdc582974f4d93d5694873ec814): | |
| 17117 | 17117 | dependencies: |
| 17118 | 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 | 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 | 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 | 17145 | react-native-drawer-layout: 4.2.4(de9b2f2dc96a3557fdc0df187a8417ee) |
| 17146 | 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 | 17148 | server-only: 0.0.1 |
| 17149 | 17149 | sf-symbols-typescript: 2.2.0 |
| 17150 | 17150 | shallowequal: 1.1.0 |
| ... | ... | @@ -19959,14 +19959,14 @@ snapshots: |
| 19959 | 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 | 19960 | optional: true |
| 19961 | 19961 | |
| 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 | 19963 | dependencies: |
| 19964 | 19964 | react: 19.2.3 |
| 19965 | 19965 | react-freeze: 1.0.4(react@19.2.3) |
| 19966 | 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 | 19967 | warn-once: 0.1.1 |
| 19968 | 19968 | |
| 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 | 19970 | dependencies: |
| 19971 | 19971 | react: 19.2.6 |
| 19972 | 19972 | react-freeze: 1.0.4(react@19.2.6) |