@capgo/capacitor-native-navigation 8.4.4 → 8.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -159,7 +159,7 @@ await NativeNavigation.setTabbar({
159
159
 
160
160
  ## Android Liquid Glass
161
161
 
162
- Enable `glass.effect: 'liquidGlass'` to draw a live blurred WebView backdrop behind Android native bars. Android 12+ uses a platform `RenderEffect` blur; older Android versions keep the translucent tint surface without live blur.
162
+ On Android 12+, floating tabbars use `liquidGlass` automatically when `setTabbar` omits `glass` (pass `glass: { effect: 'none' }` for a flat fill). The live blur uses a platform `RenderEffect` behind the bar shape; older Android versions keep a translucent tint without live blur. Tune `colors.background` alpha and `glass.surfaceAlpha` (default `0.62`) together.
163
163
 
164
164
  ```typescript
165
165
  await NativeNavigation.configure({
@@ -650,17 +650,17 @@ Global plugin configuration.
650
650
 
651
651
  Native bar colors. Use CSS-style hex strings (`#RRGGBB` or `#AARRGGBB`).
652
652
 
653
- | Prop | Type | Description |
654
- | --------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
655
- | **`dynamic`** | <code>boolean</code> | When `true`, Android 12+ derives unspecified bar colors from Material You system palettes. Explicit color fields still win. |
656
- | **`tint`** | <code>string</code> | Tint color for active buttons/items. |
657
- | **`inactiveTint`** | <code>string</code> | Color for inactive tab items. Ignored on iOS 26+ unless `experimentalBakedTintColors` is enabled. |
658
- | **`background`** | <code>string</code> | Optional background tint. Ignored on iOS 26+ so UIKit can preserve the system Liquid Glass navigation appearance. |
659
- | **`foreground`** | <code>string</code> | Title and label text color where the native platform supports it. |
660
- | **`badgeBackground`** | <code>string</code> | Badge background color for native tab badges. |
661
- | **`badgeText`** | <code>string</code> | Badge text color for native tab badges. |
662
- | **`indicator`** | <code>string</code> | Active tab indicator color on Android. |
663
- | **`ripple`** | <code>string</code> | Tab press ripple color on Android. |
653
+ | Prop | Type | Description |
654
+ | --------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
655
+ | **`dynamic`** | <code>boolean</code> | When `true`, Android 12+ derives unspecified bar colors from Material You system palettes. Explicit color fields still win. |
656
+ | **`tint`** | <code>string</code> | Tint color for active buttons/items. |
657
+ | **`inactiveTint`** | <code>string</code> | Color for inactive tab items. Ignored on iOS 26+ unless `experimentalBakedTintColors` is enabled. |
658
+ | **`background`** | <code>string</code> | Optional background tint. Ignored on iOS 26+ so UIKit can preserve the system Liquid Glass navigation appearance. On Android, alpha is honored. When `glass.effect` is not `liquidGlass`, floating tabbars still apply `glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint. |
659
+ | **`foreground`** | <code>string</code> | Title and label text color where the native platform supports it. |
660
+ | **`badgeBackground`** | <code>string</code> | Badge background color for native tab badges. |
661
+ | **`badgeText`** | <code>string</code> | Badge text color for native tab badges. |
662
+ | **`indicator`** | <code>string</code> | Active tab indicator color on Android. |
663
+ | **`ripple`** | <code>string</code> | Tab press ripple color on Android. |
664
664
 
665
665
 
666
666
  #### NativeNavigationGlassOptions
@@ -736,26 +736,26 @@ because icons are rendered by native UI.
736
736
 
737
737
  Native tabbar state.
738
738
 
739
- | Prop | Type | Description |
740
- | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
741
- | **`hidden`** | <code>boolean</code> | Hide the native tabbar. |
742
- | **`tabs`** | <code>NativeNavigationTab[]</code> | Tab definitions. |
743
- | **`selectedId`** | <code>string</code> | Currently selected tab id. |
744
- | **`labels`** | <code>boolean</code> | Show text labels. Defaults to `true`. |
745
- | **`labelVisibilityMode`** | <code><a href="#nativenavigationtablabelvisibilitymode">NativeNavigationTabLabelVisibilityMode</a></code> | Native label visibility mode. Overrides `labels` when provided. |
746
- | **`icons`** | <code>boolean</code> | Show icons. Defaults to `true`. |
747
- | **`colors`** | <code><a href="#nativenavigationcolors">NativeNavigationColors</a></code> | Tabbar color hints. |
748
- | **`blurEffect`** | <code><a href="#nativenavigationblureffect">NativeNavigationBlurEffect</a></code> | iOS blur/material effect for the tabbar background when glass is not available. |
749
- | **`glass`** | <code><a href="#nativenavigationglassoptions">NativeNavigationGlassOptions</a></code> | Optional glass background behavior. Overrides `configure({ glass })` for this tabbar update. |
750
- | **`experimentalBakedTintColors`** | <code>boolean</code> | Opt into the iOS 26 Liquid Glass tint workaround that renders active and inactive tab items into baked images. This can affect badge positioning and icon sizing, so it is disabled by default. |
751
- | **`disableTransparentOnScrollEdge`** | <code>boolean</code> | Keep the iOS scroll-edge tabbar appearance from becoming transparent. Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option. Defaults to `false`. |
752
- | **`disableIndicator`** | <code>boolean</code> | Disable the Android active tab indicator. |
753
- | **`indicatorColor`** | <code>string</code> | Active tab indicator color on Android. `colors.indicator` is also supported. |
754
- | **`rippleColor`** | <code>string</code> | Tab press ripple color on Android. `colors.ripple` is also supported. |
755
- | **`badgeBackgroundColor`** | <code>string</code> | Badge background color. `colors.badgeBackground` is also supported. |
756
- | **`badgeTextColor`** | <code>string</code> | Badge text color. `colors.badgeText` is also supported. |
757
- | **`style`** | <code><a href="#nativenavigationtabbarstyle">NativeNavigationTabbarStyle</a></code> | Optional native tabbar layout and shape customization. |
758
- | **`animated`** | <code>boolean</code> | Animate native tabbar changes. |
739
+ | Prop | Type | Description |
740
+ | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
741
+ | **`hidden`** | <code>boolean</code> | Hide the native tabbar. |
742
+ | **`tabs`** | <code>NativeNavigationTab[]</code> | Tab definitions. |
743
+ | **`selectedId`** | <code>string</code> | Currently selected tab id. |
744
+ | **`labels`** | <code>boolean</code> | Show text labels. Defaults to `true`. |
745
+ | **`labelVisibilityMode`** | <code><a href="#nativenavigationtablabelvisibilitymode">NativeNavigationTabLabelVisibilityMode</a></code> | Native label visibility mode. Overrides `labels` when provided. |
746
+ | **`icons`** | <code>boolean</code> | Show icons. Defaults to `true`. |
747
+ | **`colors`** | <code><a href="#nativenavigationcolors">NativeNavigationColors</a></code> | Tabbar color hints. |
748
+ | **`blurEffect`** | <code><a href="#nativenavigationblureffect">NativeNavigationBlurEffect</a></code> | iOS blur/material effect for the tabbar background when glass is not available. |
749
+ | **`glass`** | <code><a href="#nativenavigationglassoptions">NativeNavigationGlassOptions</a></code> | Optional glass background behavior. Overrides `configure({ glass })` for this tabbar update. On Android 12+, floating tabbars default to `liquidGlass` (live blur plus tint) when this field is omitted. Pass `effect: 'none'` for a flat translucent fill without blur. |
750
+ | **`experimentalBakedTintColors`** | <code>boolean</code> | Opt into the iOS 26 Liquid Glass tint workaround that renders active and inactive tab items into baked images. This can affect badge positioning and icon sizing, so it is disabled by default. |
751
+ | **`disableTransparentOnScrollEdge`** | <code>boolean</code> | Keep the iOS scroll-edge tabbar appearance from becoming transparent. Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option. Defaults to `false`. |
752
+ | **`disableIndicator`** | <code>boolean</code> | Disable the Android active tab indicator. |
753
+ | **`indicatorColor`** | <code>string</code> | Active tab indicator color on Android. `colors.indicator` is also supported. |
754
+ | **`rippleColor`** | <code>string</code> | Tab press ripple color on Android. `colors.ripple` is also supported. |
755
+ | **`badgeBackgroundColor`** | <code>string</code> | Badge background color. `colors.badgeBackground` is also supported. |
756
+ | **`badgeTextColor`** | <code>string</code> | Badge text color. `colors.badgeText` is also supported. |
757
+ | **`style`** | <code><a href="#nativenavigationtabbarstyle">NativeNavigationTabbarStyle</a></code> | Optional native tabbar layout and shape customization. |
758
+ | **`animated`** | <code>boolean</code> | Animate native tabbar changes. |
759
759
 
760
760
 
761
761
  #### NativeNavigationTab
@@ -81,6 +81,7 @@ public class NativeNavigationPlugin extends Plugin {
81
81
  private boolean tabbarVisible = false;
82
82
  private String contentInsetMode = "css";
83
83
  private GlassOptions defaultGlassOptions = GlassOptions.defaults();
84
+ private boolean configureSpecifiedGlassEffect = false;
84
85
  private GlassOptions navbarGlassOptions = GlassOptions.defaults();
85
86
  private GlassOptions tabbarGlassOptions = GlassOptions.defaults();
86
87
  private JSObject navbarGlassConfig;
@@ -105,6 +106,7 @@ public class NativeNavigationPlugin extends Plugin {
105
106
  private final Map<Integer, String> menuActionPlacements = new HashMap<>();
106
107
  private final List<NativeTabItem> tabItems = new ArrayList<>();
107
108
  private int selectedTabIndex = 0;
109
+ private boolean tabbarChromeHideAnimating = false;
108
110
  private boolean tabbarDisableIndicator = false;
109
111
  private Integer tabbarIndicatorColor = null;
110
112
 
@@ -112,7 +114,10 @@ public class NativeNavigationPlugin extends Plugin {
112
114
  public void load() {
113
115
  Activity activity = getActivity();
114
116
  if (activity != null) {
115
- activity.runOnUiThread(this::enableEdgeToEdge);
117
+ activity.runOnUiThread(() -> {
118
+ enableEdgeToEdge();
119
+ watchContentRootBounds();
120
+ });
116
121
  }
117
122
  }
118
123
 
@@ -121,7 +126,9 @@ public class NativeNavigationPlugin extends Plugin {
121
126
  runOnUiThread(() -> {
122
127
  enabled = call.getBoolean("enabled", true);
123
128
  contentInsetMode = call.getString("contentInsetMode", contentInsetMode);
124
- defaultGlassOptions = GlassOptions.from(call.getObject("glass", null), defaultGlassOptions);
129
+ JSObject configureGlass = call.getObject("glass", null);
130
+ configureSpecifiedGlassEffect = configureGlass != null && configureGlass.has("effect");
131
+ defaultGlassOptions = GlassOptions.from(configureGlass, defaultGlassOptions);
125
132
  navbarGlassOptions = GlassOptions.from(navbarGlassConfig, defaultGlassOptions);
126
133
  tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
127
134
  Double duration = call.getDouble("animationDuration");
@@ -215,17 +222,10 @@ public class NativeNavigationPlugin extends Plugin {
215
222
  }
216
223
 
217
224
  boolean hidden = call.getBoolean("hidden", false);
225
+ boolean animated = call.getBoolean("animated", false);
218
226
  tabbarVisible = !hidden;
219
227
  if (hidden) {
220
- if (tabbar != null) {
221
- tabbar.setVisibility(View.GONE);
222
- }
223
- if (tabbarContainer != null) {
224
- tabbarContainer.setVisibility(View.GONE);
225
- }
226
- if (tabbarBackdrop != null) {
227
- tabbarBackdrop.setVisibility(View.GONE);
228
- }
228
+ setTabbarChromeVisible(false, animated);
229
229
  updateInsetsAndNotify();
230
230
  call.resolve(insetsResult());
231
231
  return;
@@ -243,7 +243,6 @@ public class NativeNavigationPlugin extends Plugin {
243
243
  String selectedId = call.getString("selectedId", null);
244
244
  JSObject colors = call.getObject("colors", new JSObject());
245
245
  tabbarGlassConfig = call.getObject("glass", null);
246
- tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
247
246
  badgeBackgroundColor = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", Color.rgb(255, 59, 48));
248
247
  badgeTextColor = colorOption(call, colors, "badgeTextColor", "badgeText", Color.WHITE);
249
248
  tabbarDisableIndicator = call.getBoolean("disableIndicator", false);
@@ -273,6 +272,7 @@ public class NativeNavigationPlugin extends Plugin {
273
272
 
274
273
  applyTabbarColors(call, colors);
275
274
  tabbarStyle = makeTabbarStyle(call.getObject("style", new JSObject()));
275
+ tabbarGlassOptions = resolveTabbarGlassOptions(call);
276
276
 
277
277
  // Keep at most one detached trailing action for floating bars.
278
278
  // Curve bars ignore role so tab order / center selection stay stable.
@@ -314,14 +314,8 @@ public class NativeNavigationPlugin extends Plugin {
314
314
 
315
315
  applyTabbarBackground(centerTabIndex());
316
316
  renderTabbarItems(labelVisibilityMode, icons);
317
- if (tabbarContainer != null) {
318
- tabbarContainer.setVisibility(View.VISIBLE);
319
- }
320
- if (tabbarBackdrop != null) {
321
- tabbarBackdrop.setVisibility(View.VISIBLE);
322
- }
323
- nativeTabbar.setVisibility(View.VISIBLE);
324
317
  layoutChrome();
318
+ setTabbarChromeVisible(true, animated);
325
319
  updateInsetsAndNotify();
326
320
  call.resolve(insetsResult());
327
321
  });
@@ -759,6 +753,10 @@ public class NativeNavigationPlugin extends Plugin {
759
753
  tabbarContainer.setClipChildren(false);
760
754
  tabbarContainer.setClipToPadding(false);
761
755
  tabbarContainer.setElevation(dp(12));
756
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
757
+ tabbarContainer.setOutlineAmbientShadowColor(0x33000000);
758
+ tabbarContainer.setOutlineSpotShadowColor(0x66000000);
759
+ }
762
760
 
763
761
  tabbarGlassBackdrop = new GlassBackdropView(getContext());
764
762
  tabbarGlassSurface = new View(getContext());
@@ -1668,15 +1666,36 @@ public class NativeNavigationPlugin extends Plugin {
1668
1666
  }
1669
1667
  }
1670
1668
 
1671
- tabbar.setTabbarStyle(tabbarStyle, drawColor, centerIndex);
1669
+ boolean glassBlurActive =
1670
+ resolvedGlassOptions.isLiquidGlass() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
1671
+ int tabbarFillColor = glassBlurActive ? Color.TRANSPARENT : drawColor;
1672
+ tabbar.setTabbarStyle(tabbarStyle, tabbarFillColor, centerIndex);
1672
1673
  applyTabbarContainerOutline();
1673
1674
  }
1674
1675
 
1676
+ private GlassOptions resolveTabbarGlassOptions(PluginCall call) {
1677
+ if (tabbarGlassConfig != null) {
1678
+ return GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
1679
+ }
1680
+ boolean apiAtLeastS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
1681
+ if (
1682
+ TabbarChromeSupport.shouldEnableDefaultLiquidGlass(apiAtLeastS, tabbarStyle.isCurve(), false)
1683
+ && (!configureSpecifiedGlassEffect || defaultGlassOptions.isLiquidGlass())
1684
+ ) {
1685
+ return GlassOptions.from(new JSObject().put("effect", "liquidGlass"), defaultGlassOptions);
1686
+ }
1687
+ return GlassOptions.from(null, defaultGlassOptions);
1688
+ }
1689
+
1675
1690
  private int resolvedTabbarSurfaceColor() {
1676
1691
  GlassOptions resolvedGlassOptions = tabbarGlassOptions == null ? GlassOptions.defaults() : tabbarGlassOptions;
1677
- return resolvedGlassOptions.isLiquidGlass()
1678
- ? glassSurfaceColor(tabbarBackgroundColor, resolvedGlassOptions)
1679
- : tabbarBackgroundColor;
1692
+ if (resolvedGlassOptions.isLiquidGlass()) {
1693
+ return glassSurfaceColor(tabbarBackgroundColor, resolvedGlassOptions);
1694
+ }
1695
+ if (!tabbarStyle.isCurve()) {
1696
+ return glassSurfaceColor(tabbarBackgroundColor, resolvedGlassOptions);
1697
+ }
1698
+ return tabbarBackgroundColor;
1680
1699
  }
1681
1700
 
1682
1701
  private void reapplyVisibleChromeBackgrounds() {
@@ -1831,7 +1850,8 @@ public class NativeNavigationPlugin extends Plugin {
1831
1850
  int bottom = navigationBarInset();
1832
1851
  int navbarHeight = navbarVisible ? status + dp(DEFAULT_NAVBAR_DP) : 0;
1833
1852
  int tabbarHeight = dp(tabbarStyle.totalHeight());
1834
- int tabbarBottomMargin = tabbarVisible ? bottom + dp(tabbarStyle.bottomGap) : bottom;
1853
+ boolean tabbarLayoutVisible = tabbarVisible || tabbarChromeHideAnimating;
1854
+ int tabbarBottomMargin = tabbarLayoutVisible ? bottom + dp(tabbarStyle.bottomGap) : bottom;
1835
1855
 
1836
1856
  if (navbarContainer != null) {
1837
1857
  FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(
@@ -1854,7 +1874,7 @@ public class NativeNavigationPlugin extends Plugin {
1854
1874
  if (tabbarBackdrop != null) {
1855
1875
  // Only the edge-to-edge curve bar extends its surface under the gesture area. A floating
1856
1876
  // tabbar must leave the page visible below it, or it sits on a solid band.
1857
- int backdropHeight = tabbarVisible && tabbarStyle.isCurve() ? bottom + dp(tabbarStyle.bottomGap) : 0;
1877
+ int backdropHeight = tabbarLayoutVisible && tabbarStyle.isCurve() ? bottom + dp(tabbarStyle.bottomGap) : 0;
1858
1878
  FrameLayout.LayoutParams backdropParams = new FrameLayout.LayoutParams(
1859
1879
  ViewGroup.LayoutParams.MATCH_PARENT,
1860
1880
  backdropHeight,
@@ -1862,7 +1882,7 @@ public class NativeNavigationPlugin extends Plugin {
1862
1882
  );
1863
1883
  tabbarBackdrop.setLayoutParams(backdropParams);
1864
1884
  tabbarBackdrop.setBackgroundColor(resolvedTabbarSurfaceColor());
1865
- tabbarBackdrop.setVisibility(tabbarVisible && backdropHeight > 0 ? View.VISIBLE : View.GONE);
1885
+ tabbarBackdrop.setVisibility(tabbarLayoutVisible && backdropHeight > 0 ? View.VISIBLE : View.GONE);
1866
1886
  }
1867
1887
  if (tabbarContainer != null) {
1868
1888
  int rootWidth = root.getWidth() > 0 ? root.getWidth() : Resources.getSystem().getDisplayMetrics().widthPixels;
@@ -1904,6 +1924,97 @@ public class NativeNavigationPlugin extends Plugin {
1904
1924
  bringChromeToFront();
1905
1925
  }
1906
1926
 
1927
+ private void setTabbarChromeVisible(boolean visible, boolean animated) {
1928
+ if (!animated) {
1929
+ tabbarChromeHideAnimating = false;
1930
+ int visibility = visible ? View.VISIBLE : View.GONE;
1931
+ if (tabbar != null) {
1932
+ tabbar.setVisibility(visibility);
1933
+ }
1934
+ if (tabbarContainer != null) {
1935
+ tabbarContainer.animate().cancel();
1936
+ tabbarContainer.setTranslationY(0f);
1937
+ tabbarContainer.setAlpha(1f);
1938
+ tabbarContainer.setVisibility(visibility);
1939
+ }
1940
+ if (tabbarBackdrop != null) {
1941
+ tabbarBackdrop.animate().cancel();
1942
+ tabbarBackdrop.setAlpha(1f);
1943
+ tabbarBackdrop.setVisibility(visible && tabbarVisible ? View.VISIBLE : View.GONE);
1944
+ }
1945
+ return;
1946
+ }
1947
+
1948
+ if (tabbarContainer == null) {
1949
+ setTabbarChromeVisible(visible, false);
1950
+ return;
1951
+ }
1952
+
1953
+ float slideDistance = tabbarSlideDistance();
1954
+ tabbarContainer.animate().cancel();
1955
+ if (tabbarBackdrop != null) {
1956
+ tabbarBackdrop.animate().cancel();
1957
+ }
1958
+
1959
+ if (!visible) {
1960
+ tabbarChromeHideAnimating = true;
1961
+ tabbarContainer
1962
+ .animate()
1963
+ .translationY(slideDistance)
1964
+ .alpha(0f)
1965
+ .setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS)
1966
+ .withEndAction(() -> {
1967
+ tabbarChromeHideAnimating = false;
1968
+ if (tabbar != null) {
1969
+ tabbar.setVisibility(View.GONE);
1970
+ }
1971
+ tabbarContainer.setVisibility(View.GONE);
1972
+ if (tabbarBackdrop != null) {
1973
+ tabbarBackdrop.setVisibility(View.GONE);
1974
+ }
1975
+ })
1976
+ .start();
1977
+ if (tabbarBackdrop != null) {
1978
+ tabbarBackdrop.animate().alpha(0f).setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS).start();
1979
+ }
1980
+ return;
1981
+ }
1982
+
1983
+ tabbarChromeHideAnimating = false;
1984
+ tabbarContainer.setVisibility(View.VISIBLE);
1985
+ tabbarContainer.setAlpha(0f);
1986
+ tabbarContainer.setTranslationY(slideDistance);
1987
+ if (tabbarBackdrop != null) {
1988
+ tabbarBackdrop.setVisibility(View.VISIBLE);
1989
+ tabbarBackdrop.setAlpha(0f);
1990
+ }
1991
+ if (tabbar != null) {
1992
+ tabbar.setVisibility(View.VISIBLE);
1993
+ }
1994
+ tabbarContainer
1995
+ .animate()
1996
+ .translationY(0f)
1997
+ .alpha(1f)
1998
+ .setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS)
1999
+ .start();
2000
+ if (tabbarBackdrop != null) {
2001
+ tabbarBackdrop.animate().alpha(1f).setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS).start();
2002
+ }
2003
+ }
2004
+
2005
+ private float tabbarSlideDistance() {
2006
+ if (tabbarContainer == null) {
2007
+ return dp(tabbarStyle.totalHeight() + tabbarStyle.bottomGap);
2008
+ }
2009
+ ViewGroup.LayoutParams params = tabbarContainer.getLayoutParams();
2010
+ int bottomMargin = params instanceof ViewGroup.MarginLayoutParams ? ((ViewGroup.MarginLayoutParams) params).bottomMargin : 0;
2011
+ return TabbarChromeSupport.tabbarSlideDistancePx(
2012
+ tabbarContainer.getHeight(),
2013
+ bottomMargin,
2014
+ dp(tabbarStyle.totalHeight() + tabbarStyle.bottomGap)
2015
+ );
2016
+ }
2017
+
1907
2018
  private void bringChromeToFront() {
1908
2019
  if (navbarContainer != null) {
1909
2020
  navbarContainer.bringToFront();
@@ -2062,7 +2173,21 @@ public class NativeNavigationPlugin extends Plugin {
2062
2173
  }
2063
2174
  }
2064
2175
 
2176
+ /**
2177
+ * The chrome is laid out inside {@code android.R.id.content}, so it only needs to clear the part of a
2178
+ * system bar that actually overlaps that view. Capacitor's SystemBars plugin (default
2179
+ * {@code insetsHandling: 'css'} without {@code viewport-fit=cover}) pads the decor view by the system bars,
2180
+ * which already moves the content root clear of them; adding the full inset again doubled the gap.
2181
+ */
2065
2182
  private int statusBarInset() {
2183
+ return Math.max(0, rawStatusBarInset() - contentRootTopOffset());
2184
+ }
2185
+
2186
+ private int navigationBarInset() {
2187
+ return Math.max(0, rawNavigationBarInset() - contentRootBottomOffset());
2188
+ }
2189
+
2190
+ private int rawStatusBarInset() {
2066
2191
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
2067
2192
  WindowInsets insets = getActivity().getWindow().getDecorView().getRootWindowInsets();
2068
2193
  if (insets != null) {
@@ -2072,7 +2197,7 @@ public class NativeNavigationPlugin extends Plugin {
2072
2197
  return systemDimension("status_bar_height");
2073
2198
  }
2074
2199
 
2075
- private int navigationBarInset() {
2200
+ private int rawNavigationBarInset() {
2076
2201
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
2077
2202
  WindowInsets insets = getActivity().getWindow().getDecorView().getRootWindowInsets();
2078
2203
  if (insets != null) {
@@ -2082,6 +2207,45 @@ public class NativeNavigationPlugin extends Plugin {
2082
2207
  return systemDimension("navigation_bar_height");
2083
2208
  }
2084
2209
 
2210
+ private int contentRootTopOffset() {
2211
+ FrameLayout root = contentRoot();
2212
+ if (root == null || !root.isLaidOut()) {
2213
+ return 0;
2214
+ }
2215
+ int[] location = new int[2];
2216
+ root.getLocationInWindow(location);
2217
+ return Math.max(0, location[1]);
2218
+ }
2219
+
2220
+ private int contentRootBottomOffset() {
2221
+ FrameLayout root = contentRoot();
2222
+ Activity activity = getActivity();
2223
+ if (root == null || activity == null || !root.isLaidOut()) {
2224
+ return 0;
2225
+ }
2226
+ View decor = activity.getWindow().getDecorView();
2227
+ int[] location = new int[2];
2228
+ root.getLocationInWindow(location);
2229
+ return Math.max(0, decor.getHeight() - (location[1] + root.getHeight()));
2230
+ }
2231
+
2232
+ private void watchContentRootBounds() {
2233
+ FrameLayout root = contentRoot();
2234
+ if (root == null) {
2235
+ return;
2236
+ }
2237
+ // SystemBars can add or drop its decor padding after load (for example once viewport-fit=cover is
2238
+ // detected), which moves the content root without a call into this plugin. Re-run layout when it moves.
2239
+ root.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
2240
+ if (top == oldTop && bottom == oldBottom && left == oldLeft && right == oldRight) {
2241
+ return;
2242
+ }
2243
+ if (navbarVisible || tabbarVisible) {
2244
+ view.post(this::updateInsetsAndNotify);
2245
+ }
2246
+ });
2247
+ }
2248
+
2085
2249
  private int systemDimension(String name) {
2086
2250
  int id = getContext().getResources().getIdentifier(name, "dimen", "android");
2087
2251
  return id == 0 ? 0 : getContext().getResources().getDimensionPixelSize(id);
@@ -6,6 +6,8 @@ import android.os.Build;
6
6
 
7
7
  public final class TabbarChromeSupport {
8
8
 
9
+ public static final int TABBAR_VISIBILITY_ANIMATION_MS = 200;
10
+
9
11
  public enum TabbarOutlineMode {
10
12
  FLOATING_SHAPE,
11
13
  CURVE_DEFAULT
@@ -13,6 +15,13 @@ public final class TabbarChromeSupport {
13
15
 
14
16
  private TabbarChromeSupport() {}
15
17
 
18
+ public static float tabbarSlideDistancePx(int containerHeight, int bottomMargin, int fallbackDistance) {
19
+ if (containerHeight > 0) {
20
+ return (float) containerHeight + (float) bottomMargin;
21
+ }
22
+ return (float) fallbackDistance;
23
+ }
24
+
16
25
  public static int resolveSelectedIndicatorColor(Integer explicitColor, int tintColor) {
17
26
  if (explicitColor != null) {
18
27
  return explicitColor;
@@ -28,6 +37,10 @@ public final class TabbarChromeSupport {
28
37
  return mode == TabbarOutlineMode.FLOATING_SHAPE;
29
38
  }
30
39
 
40
+ public static boolean shouldEnableDefaultLiquidGlass(boolean apiAtLeastS, boolean isCurve, boolean glassSpecified) {
41
+ return !glassSpecified && !isCurve && apiAtLeastS;
42
+ }
43
+
31
44
  public static int floatingCapsuleWidth(int totalWidth, int barHeightPx, int trailingGapPx, boolean hasDetachedTrailing) {
32
45
  if (!hasDetachedTrailing) {
33
46
  return totalWidth;
package/dist/docs.json CHANGED
@@ -419,7 +419,7 @@
419
419
  {
420
420
  "name": "background",
421
421
  "tags": [],
422
- "docs": "Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\nsystem Liquid Glass navigation appearance.",
422
+ "docs": "Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\nsystem Liquid Glass navigation appearance. On Android, alpha is honored.\nWhen `glass.effect` is not `liquidGlass`, floating tabbars still apply\n`glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint.",
423
423
  "complexTypes": [],
424
424
  "type": "string | undefined"
425
425
  },
@@ -791,7 +791,7 @@
791
791
  {
792
792
  "name": "glass",
793
793
  "tags": [],
794
- "docs": "Optional glass background behavior. Overrides `configure({ glass })` for\nthis tabbar update.",
794
+ "docs": "Optional glass background behavior. Overrides `configure({ glass })` for\nthis tabbar update. On Android 12+, floating tabbars default to\n`liquidGlass` (live blur plus tint) when this field is omitted. Pass\n`effect: 'none'` for a flat translucent fill without blur.",
795
795
  "complexTypes": [
796
796
  "NativeNavigationGlassOptions"
797
797
  ],
@@ -150,7 +150,9 @@ export interface NativeNavigationColors {
150
150
  inactiveTint?: string;
151
151
  /**
152
152
  * Optional background tint. Ignored on iOS 26+ so UIKit can preserve the
153
- * system Liquid Glass navigation appearance.
153
+ * system Liquid Glass navigation appearance. On Android, alpha is honored.
154
+ * When `glass.effect` is not `liquidGlass`, floating tabbars still apply
155
+ * `glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint.
154
156
  */
155
157
  background?: string;
156
158
  /**
@@ -439,7 +441,9 @@ export interface NativeNavigationTabbarOptions {
439
441
  blurEffect?: NativeNavigationBlurEffect;
440
442
  /**
441
443
  * Optional glass background behavior. Overrides `configure({ glass })` for
442
- * this tabbar update.
444
+ * this tabbar update. On Android 12+, floating tabbars default to
445
+ * `liquidGlass` (live blur plus tint) when this field is omitted. Pass
446
+ * `effect: 'none'` for a flat translucent fill without blur.
443
447
  */
444
448
  glass?: NativeNavigationGlassOptions;
445
449
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Platform rendering preference for the native bars.\n */\nexport type NativeNavigationPlatformStyle = 'auto' | 'ios' | 'android';\n\n/**\n * How the plugin exposes native bar sizes to web content.\n */\nexport type NativeNavigationContentInsetMode = 'css' | 'none';\n\n/**\n * Navigation animation direction.\n */\nexport type NativeNavigationTransitionDirection = 'forward' | 'back' | 'root' | 'tab' | 'zoom' | 'none';\n\n/**\n * Native material/blur effect preference.\n */\nexport type NativeNavigationBlurEffect =\n | 'none'\n | 'systemDefault'\n | 'extraLight'\n | 'light'\n | 'dark'\n | 'regular'\n | 'prominent'\n | 'systemUltraThinMaterial'\n | 'systemThinMaterial'\n | 'systemMaterial'\n | 'systemThickMaterial'\n | 'systemChromeMaterial'\n | 'systemUltraThinMaterialLight'\n | 'systemThinMaterialLight'\n | 'systemMaterialLight'\n | 'systemThickMaterialLight'\n | 'systemChromeMaterialLight'\n | 'systemUltraThinMaterialDark'\n | 'systemThinMaterialDark'\n | 'systemMaterialDark'\n | 'systemThickMaterialDark'\n | 'systemChromeMaterialDark';\n\n/**\n * Native glass background rendering preference.\n */\nexport type NativeNavigationGlassEffect = 'none' | 'liquidGlass';\n\n/**\n * Native glass background configuration.\n */\nexport interface NativeNavigationGlassOptions {\n /**\n * `liquidGlass` enables the Android 12+ live blurred WebView backdrop for\n * native bars. Android 11 and older keep a translucent surface fallback. iOS\n * uses the platform-owned Liquid Glass behavior.\n */\n effect?: NativeNavigationGlassEffect;\n\n /**\n * Android blur radius in native dp for `liquidGlass`. Defaults to `18`.\n */\n blurRadius?: number;\n\n /**\n * Alpha multiplier for the tint surface over the glass backdrop. Defaults to\n * `0.62`.\n */\n surfaceAlpha?: number;\n}\n\n/**\n * Native tab label visibility behavior.\n */\nexport type NativeNavigationTabLabelVisibilityMode = 'auto' | 'selected' | 'labeled' | 'unlabeled';\n\n/**\n * A rectangle in WebView viewport coordinates, expressed in native points/dp.\n */\nexport interface NativeNavigationRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Native tabbar background shape.\n */\nexport type NativeNavigationTabbarShape = 'floating' | 'curve';\n\n/**\n * Native tab role for Liquid Glass tab bars.\n *\n * `search` (and `prominent` when the OS supports it) renders as a detached\n * trailing circular action beside the floating tab capsule — the Apple News /\n * Photos pattern. Only one detached trailing role is used; if multiple tabs\n * set `search` or `prominent`, the last one wins. Curve-shaped bars ignore\n * `role` and keep using the included center action instead.\n */\nexport type NativeNavigationTabRole = 'normal' | 'search' | 'prominent';\n\n/**\n * A serializable icon descriptor. Framework nodes are intentionally not accepted\n * because icons are rendered by native UI.\n */\nexport interface NativeNavigationIcon {\n /**\n * Cross-platform asset path or URL fallback.\n */\n src?: string;\n\n /**\n * Cross-platform inline SVG markup. The native renderers support common icon\n * shapes such as path, line, polyline, polygon, circle, and rect. SVG icons\n * are rendered as template images by default so native tint colors still\n * apply.\n */\n svg?: string;\n\n /**\n * Preferred rendered icon width in native points/dp. Defaults to `24`.\n */\n width?: number;\n\n /**\n * Preferred rendered icon height in native points/dp. Defaults to `24`.\n */\n height?: number;\n\n /**\n * When `true`, native tint colors are applied to the rendered SVG/image.\n * Defaults to `true`.\n */\n template?: boolean;\n\n /**\n * iOS-specific SF Symbol, bundled image name, or inline SVG.\n */\n ios?: {\n /**\n * SF Symbol name, for example `house.fill`.\n */\n sfSymbol?: string;\n\n /**\n * Bundled image name from the app asset catalog.\n */\n image?: string;\n\n /**\n * iOS-specific inline SVG markup.\n */\n svg?: string;\n };\n\n /**\n * Android-specific drawable resource, asset name, or inline SVG.\n */\n android?: {\n /**\n * Drawable resource name without the `R.drawable.` prefix.\n */\n resource?: string;\n\n /**\n * Bundled image asset name.\n */\n image?: string;\n\n /**\n * Android-specific inline SVG markup.\n */\n svg?: string;\n };\n}\n\n/**\n * Native bar colors. Use CSS-style hex strings (`#RRGGBB` or `#AARRGGBB`).\n */\nexport interface NativeNavigationColors {\n /**\n * When `true`, Android 12+ derives unspecified bar colors from Material You\n * system palettes. Explicit color fields still win.\n */\n dynamic?: boolean;\n\n /**\n * Tint color for active buttons/items.\n */\n tint?: string;\n\n /**\n * Color for inactive tab items. Ignored on iOS 26+ unless\n * `experimentalBakedTintColors` is enabled.\n */\n inactiveTint?: string;\n\n /**\n * Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\n * system Liquid Glass navigation appearance.\n */\n background?: string;\n\n /**\n * Title and label text color where the native platform supports it.\n */\n foreground?: string;\n\n /**\n * Badge background color for native tab badges.\n */\n badgeBackground?: string;\n\n /**\n * Badge text color for native tab badges.\n */\n badgeText?: string;\n\n /**\n * Active tab indicator color on Android.\n */\n indicator?: string;\n\n /**\n * Tab press ripple color on Android.\n */\n ripple?: string;\n}\n\n/**\n * Global plugin configuration.\n */\nexport interface NativeNavigationConfigureOptions {\n /**\n * Enables or disables the native chrome host.\n */\n enabled?: boolean;\n\n /**\n * Native style preference. `auto` uses the current platform.\n */\n platformStyle?: NativeNavigationPlatformStyle;\n\n /**\n * When `css`, the plugin writes CSS variables on `document.documentElement`.\n */\n contentInsetMode?: NativeNavigationContentInsetMode;\n\n /**\n * Default native transition duration in milliseconds.\n */\n animationDuration?: number;\n\n /**\n * Shared color hints for native bars.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Shared glass background defaults for native bars.\n */\n glass?: NativeNavigationGlassOptions;\n}\n\n/**\n * A button shown in the native navbar.\n */\nexport interface NativeNavigationBarButton {\n /**\n * Stable id returned in `navbarItemTap`.\n */\n id: string;\n\n /**\n * Visible text label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Whether the action is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n}\n\n/**\n * Native back button configuration.\n */\nexport interface NativeNavigationBackButton {\n /**\n * Show the native back affordance.\n */\n visible?: boolean;\n\n /**\n * Optional back title.\n */\n title?: string;\n}\n\n/**\n * Native navbar state.\n */\nexport interface NativeNavigationNavbarOptions {\n /**\n * Hide the native navbar.\n */\n hidden?: boolean;\n\n /**\n * Main title.\n */\n title?: string;\n\n /**\n * Secondary title where supported by the platform.\n */\n subtitle?: string;\n\n /**\n * Prefer a large iOS title style.\n */\n large?: boolean;\n\n /**\n * Prefer transparent/scroll-edge style.\n */\n transparent?: boolean;\n\n /**\n * iOS blur/material effect for the navbar background when glass is not\n * available. Defaults to `systemChromeMaterial` for transparent bars.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this navbar update.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Back button state.\n */\n backButton?: NativeNavigationBackButton;\n\n /**\n * Left-side action buttons.\n */\n leftItems?: NativeNavigationBarButton[];\n\n /**\n * Right-side action buttons.\n */\n rightItems?: NativeNavigationBarButton[];\n\n /**\n * Navbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Animate native navbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * A native tab item.\n */\nexport interface NativeNavigationTab {\n /**\n * Stable tab id returned in `tabSelect`.\n */\n id: string;\n\n /**\n * Visible tab label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Optional selected-state icon.\n */\n selectedIcon?: NativeNavigationIcon;\n\n /**\n * Optional badge. Numeric badges are supported on both platforms; text badge\n * support depends on platform capabilities.\n */\n badge?: string | number;\n\n /**\n * Whether the tab is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n\n /**\n * Hide the tab item from the native tabbar. When the hidden tab is selected,\n * native platform constraints may keep it visible until another tab is\n * selected.\n */\n hidden?: boolean;\n\n /**\n * Optional tab role. On floating tabbars, `search` (or `prominent` when\n * available) becomes a detached trailing circular action beside the capsule\n * — including the iOS 26+ system Liquid Glass tab bar, Android floating\n * layout, and the custom floating capsule path. Only one detached trailing\n * role is used; if multiple tabs set `search` or `prominent`, the last one\n * wins. Curve-shaped bars ignore `role` and keep using the included center\n * action. Defaults to `normal`.\n */\n role?: NativeNavigationTabRole;\n}\n\n/**\n * Native tabbar layout and background shape options.\n */\nexport interface NativeNavigationTabbarStyle {\n /**\n * `floating` keeps the capsule tabbar. On iOS 26+ this uses the\n * system-owned Liquid Glass `UITabBarController` unless a custom capsule\n * path is required; earlier iOS and the custom capsule path use\n * `UIGlassEffect` on iOS 26+ (blur material fallback otherwise). `curve`\n * draws a full-width bar with an included center action.\n */\n shape?: NativeNavigationTabbarShape;\n\n /**\n * Bar height in native points/dp, not including the system bottom inset.\n * Curve defaults to `49` on iOS (83pt once the home indicator is added) and\n * `80` on Android (Material navigation bar). Floating defaults to `64`.\n * Pass `height` to override either platform.\n */\n height?: number;\n\n /**\n * Horizontal margin in native points/dp. Defaults to `24` for `floating` and\n * `0` for `curve`.\n */\n horizontalMargin?: number;\n\n /**\n * Maximum tabbar width in native points/dp. Defaults to `430` for `floating`;\n * `curve` uses the available width unless this is set.\n */\n maxWidth?: number;\n\n /**\n * Bottom gap above the platform safe area in native points/dp. Defaults to\n * `10` for `floating` and `0` for `curve`.\n */\n bottomGap?: number;\n\n /**\n * Background corner radius in native points/dp. Defaults to a capsule radius\n * for `floating` and `0` for `curve`.\n */\n cornerRadius?: number;\n\n /**\n * Tab id promoted into the included center button for `curve`. Defaults to\n * the middle tab.\n */\n centerItemId?: string;\n\n /**\n * Included center button diameter in native points/dp. Defaults to `56`.\n */\n centerButtonDiameter?: number;\n\n /**\n * Distance from the top of the center button to the top edge of the bar in\n * native points/dp. Defaults to half of `centerButtonDiameter`.\n */\n centerButtonLift?: number;\n\n /**\n * Included center button color. Defaults to the active tint color.\n */\n centerButtonColor?: string;\n\n /**\n * Included center button icon color. Defaults to white.\n */\n centerButtonIconColor?: string;\n}\n\n/**\n * Native tabbar state.\n */\nexport interface NativeNavigationTabbarOptions {\n /**\n * Hide the native tabbar.\n */\n hidden?: boolean;\n\n /**\n * Tab definitions.\n */\n tabs?: NativeNavigationTab[];\n\n /**\n * Currently selected tab id.\n */\n selectedId?: string;\n\n /**\n * Show text labels. Defaults to `true`.\n */\n labels?: boolean;\n\n /**\n * Native label visibility mode. Overrides `labels` when provided.\n */\n labelVisibilityMode?: NativeNavigationTabLabelVisibilityMode;\n\n /**\n * Show icons. Defaults to `true`.\n */\n icons?: boolean;\n\n /**\n * Tabbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * iOS blur/material effect for the tabbar background when glass is not\n * available.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this tabbar update.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Opt into the iOS 26 Liquid Glass tint workaround that renders active and\n * inactive tab items into baked images. This can affect badge positioning\n * and icon sizing, so it is disabled by default.\n */\n experimentalBakedTintColors?: boolean;\n\n /**\n * Keep the iOS scroll-edge tabbar appearance from becoming transparent.\n * Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option.\n * Defaults to `false`.\n */\n disableTransparentOnScrollEdge?: boolean;\n\n /**\n * Disable the Android active tab indicator.\n */\n disableIndicator?: boolean;\n\n /**\n * Active tab indicator color on Android. `colors.indicator` is also\n * supported.\n */\n indicatorColor?: string;\n\n /**\n * Tab press ripple color on Android. `colors.ripple` is also supported.\n */\n rippleColor?: string;\n\n /**\n * Badge background color. `colors.badgeBackground` is also supported.\n */\n badgeBackgroundColor?: string;\n\n /**\n * Badge text color. `colors.badgeText` is also supported.\n */\n badgeTextColor?: string;\n\n /**\n * Optional native tabbar layout and shape customization.\n */\n style?: NativeNavigationTabbarStyle;\n\n /**\n * Animate native tabbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * Insets exposed to web content.\n */\nexport interface NativeNavigationInsets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n navbarHeight: number;\n tabbarHeight: number;\n}\n\n/**\n * Returned by methods that may change safe content bounds.\n */\nexport interface NativeNavigationInsetsResult {\n insets: NativeNavigationInsets;\n}\n\n/**\n * Begin a native transition transaction before JS changes route content.\n */\nexport interface NativeNavigationBeginTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions. Use viewport coordinates such as\n * those returned by `Element.getBoundingClientRect()`.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Finish a native transition transaction after JS has changed route content.\n */\nexport interface NativeNavigationFinishTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions when no active source was recorded.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Native transition result.\n */\nexport interface NativeNavigationTransitionResult {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Plugin version payload.\n */\nexport interface PluginVersionResult {\n /**\n * Version identifier returned by the platform implementation.\n */\n version: string;\n}\n\nexport interface NativeNavigationBackEvent {\n source: 'navbar';\n}\n\nexport interface NativeNavigationBarItemTapEvent {\n id: string;\n title?: string;\n placement: 'left' | 'right';\n}\n\nexport interface NativeNavigationTabSelectEvent {\n id: string;\n index: number;\n title?: string;\n}\n\nexport interface NativeNavigationSafeAreaChangedEvent {\n insets: NativeNavigationInsets;\n}\n\nexport interface NativeNavigationTransitionEvent {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Framework-agnostic native navigation chrome API.\n */\nexport interface NativeNavigationPlugin {\n /**\n * Configure the native chrome host and content inset behavior.\n */\n configure(options?: NativeNavigationConfigureOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native navbar.\n */\n setNavbar(options: NativeNavigationNavbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native tabbar.\n */\n setTabbar(options: NativeNavigationTabbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Capture the current WebView and prepare a native transition.\n */\n beginTransition(options?: NativeNavigationBeginTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Animate from the captured WebView snapshot to the current live WebView.\n */\n finishTransition(options?: NativeNavigationFinishTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Returns the platform implementation version marker.\n */\n getPluginVersion(): Promise<PluginVersionResult>;\n\n addListener(\n eventName: 'navbarBack',\n listenerFunc: (event: NativeNavigationBackEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'navbarItemTap',\n listenerFunc: (event: NativeNavigationBarItemTapEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'tabSelect',\n listenerFunc: (event: NativeNavigationTabSelectEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'safeAreaChanged',\n listenerFunc: (event: NativeNavigationSafeAreaChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionStart',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionEnd',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Platform rendering preference for the native bars.\n */\nexport type NativeNavigationPlatformStyle = 'auto' | 'ios' | 'android';\n\n/**\n * How the plugin exposes native bar sizes to web content.\n */\nexport type NativeNavigationContentInsetMode = 'css' | 'none';\n\n/**\n * Navigation animation direction.\n */\nexport type NativeNavigationTransitionDirection = 'forward' | 'back' | 'root' | 'tab' | 'zoom' | 'none';\n\n/**\n * Native material/blur effect preference.\n */\nexport type NativeNavigationBlurEffect =\n | 'none'\n | 'systemDefault'\n | 'extraLight'\n | 'light'\n | 'dark'\n | 'regular'\n | 'prominent'\n | 'systemUltraThinMaterial'\n | 'systemThinMaterial'\n | 'systemMaterial'\n | 'systemThickMaterial'\n | 'systemChromeMaterial'\n | 'systemUltraThinMaterialLight'\n | 'systemThinMaterialLight'\n | 'systemMaterialLight'\n | 'systemThickMaterialLight'\n | 'systemChromeMaterialLight'\n | 'systemUltraThinMaterialDark'\n | 'systemThinMaterialDark'\n | 'systemMaterialDark'\n | 'systemThickMaterialDark'\n | 'systemChromeMaterialDark';\n\n/**\n * Native glass background rendering preference.\n */\nexport type NativeNavigationGlassEffect = 'none' | 'liquidGlass';\n\n/**\n * Native glass background configuration.\n */\nexport interface NativeNavigationGlassOptions {\n /**\n * `liquidGlass` enables the Android 12+ live blurred WebView backdrop for\n * native bars. Android 11 and older keep a translucent surface fallback. iOS\n * uses the platform-owned Liquid Glass behavior.\n */\n effect?: NativeNavigationGlassEffect;\n\n /**\n * Android blur radius in native dp for `liquidGlass`. Defaults to `18`.\n */\n blurRadius?: number;\n\n /**\n * Alpha multiplier for the tint surface over the glass backdrop. Defaults to\n * `0.62`.\n */\n surfaceAlpha?: number;\n}\n\n/**\n * Native tab label visibility behavior.\n */\nexport type NativeNavigationTabLabelVisibilityMode = 'auto' | 'selected' | 'labeled' | 'unlabeled';\n\n/**\n * A rectangle in WebView viewport coordinates, expressed in native points/dp.\n */\nexport interface NativeNavigationRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Native tabbar background shape.\n */\nexport type NativeNavigationTabbarShape = 'floating' | 'curve';\n\n/**\n * Native tab role for Liquid Glass tab bars.\n *\n * `search` (and `prominent` when the OS supports it) renders as a detached\n * trailing circular action beside the floating tab capsule — the Apple News /\n * Photos pattern. Only one detached trailing role is used; if multiple tabs\n * set `search` or `prominent`, the last one wins. Curve-shaped bars ignore\n * `role` and keep using the included center action instead.\n */\nexport type NativeNavigationTabRole = 'normal' | 'search' | 'prominent';\n\n/**\n * A serializable icon descriptor. Framework nodes are intentionally not accepted\n * because icons are rendered by native UI.\n */\nexport interface NativeNavigationIcon {\n /**\n * Cross-platform asset path or URL fallback.\n */\n src?: string;\n\n /**\n * Cross-platform inline SVG markup. The native renderers support common icon\n * shapes such as path, line, polyline, polygon, circle, and rect. SVG icons\n * are rendered as template images by default so native tint colors still\n * apply.\n */\n svg?: string;\n\n /**\n * Preferred rendered icon width in native points/dp. Defaults to `24`.\n */\n width?: number;\n\n /**\n * Preferred rendered icon height in native points/dp. Defaults to `24`.\n */\n height?: number;\n\n /**\n * When `true`, native tint colors are applied to the rendered SVG/image.\n * Defaults to `true`.\n */\n template?: boolean;\n\n /**\n * iOS-specific SF Symbol, bundled image name, or inline SVG.\n */\n ios?: {\n /**\n * SF Symbol name, for example `house.fill`.\n */\n sfSymbol?: string;\n\n /**\n * Bundled image name from the app asset catalog.\n */\n image?: string;\n\n /**\n * iOS-specific inline SVG markup.\n */\n svg?: string;\n };\n\n /**\n * Android-specific drawable resource, asset name, or inline SVG.\n */\n android?: {\n /**\n * Drawable resource name without the `R.drawable.` prefix.\n */\n resource?: string;\n\n /**\n * Bundled image asset name.\n */\n image?: string;\n\n /**\n * Android-specific inline SVG markup.\n */\n svg?: string;\n };\n}\n\n/**\n * Native bar colors. Use CSS-style hex strings (`#RRGGBB` or `#AARRGGBB`).\n */\nexport interface NativeNavigationColors {\n /**\n * When `true`, Android 12+ derives unspecified bar colors from Material You\n * system palettes. Explicit color fields still win.\n */\n dynamic?: boolean;\n\n /**\n * Tint color for active buttons/items.\n */\n tint?: string;\n\n /**\n * Color for inactive tab items. Ignored on iOS 26+ unless\n * `experimentalBakedTintColors` is enabled.\n */\n inactiveTint?: string;\n\n /**\n * Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\n * system Liquid Glass navigation appearance. On Android, alpha is honored.\n * When `glass.effect` is not `liquidGlass`, floating tabbars still apply\n * `glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint.\n */\n background?: string;\n\n /**\n * Title and label text color where the native platform supports it.\n */\n foreground?: string;\n\n /**\n * Badge background color for native tab badges.\n */\n badgeBackground?: string;\n\n /**\n * Badge text color for native tab badges.\n */\n badgeText?: string;\n\n /**\n * Active tab indicator color on Android.\n */\n indicator?: string;\n\n /**\n * Tab press ripple color on Android.\n */\n ripple?: string;\n}\n\n/**\n * Global plugin configuration.\n */\nexport interface NativeNavigationConfigureOptions {\n /**\n * Enables or disables the native chrome host.\n */\n enabled?: boolean;\n\n /**\n * Native style preference. `auto` uses the current platform.\n */\n platformStyle?: NativeNavigationPlatformStyle;\n\n /**\n * When `css`, the plugin writes CSS variables on `document.documentElement`.\n */\n contentInsetMode?: NativeNavigationContentInsetMode;\n\n /**\n * Default native transition duration in milliseconds.\n */\n animationDuration?: number;\n\n /**\n * Shared color hints for native bars.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Shared glass background defaults for native bars.\n */\n glass?: NativeNavigationGlassOptions;\n}\n\n/**\n * A button shown in the native navbar.\n */\nexport interface NativeNavigationBarButton {\n /**\n * Stable id returned in `navbarItemTap`.\n */\n id: string;\n\n /**\n * Visible text label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Whether the action is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n}\n\n/**\n * Native back button configuration.\n */\nexport interface NativeNavigationBackButton {\n /**\n * Show the native back affordance.\n */\n visible?: boolean;\n\n /**\n * Optional back title.\n */\n title?: string;\n}\n\n/**\n * Native navbar state.\n */\nexport interface NativeNavigationNavbarOptions {\n /**\n * Hide the native navbar.\n */\n hidden?: boolean;\n\n /**\n * Main title.\n */\n title?: string;\n\n /**\n * Secondary title where supported by the platform.\n */\n subtitle?: string;\n\n /**\n * Prefer a large iOS title style.\n */\n large?: boolean;\n\n /**\n * Prefer transparent/scroll-edge style.\n */\n transparent?: boolean;\n\n /**\n * iOS blur/material effect for the navbar background when glass is not\n * available. Defaults to `systemChromeMaterial` for transparent bars.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this navbar update.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Back button state.\n */\n backButton?: NativeNavigationBackButton;\n\n /**\n * Left-side action buttons.\n */\n leftItems?: NativeNavigationBarButton[];\n\n /**\n * Right-side action buttons.\n */\n rightItems?: NativeNavigationBarButton[];\n\n /**\n * Navbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Animate native navbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * A native tab item.\n */\nexport interface NativeNavigationTab {\n /**\n * Stable tab id returned in `tabSelect`.\n */\n id: string;\n\n /**\n * Visible tab label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Optional selected-state icon.\n */\n selectedIcon?: NativeNavigationIcon;\n\n /**\n * Optional badge. Numeric badges are supported on both platforms; text badge\n * support depends on platform capabilities.\n */\n badge?: string | number;\n\n /**\n * Whether the tab is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n\n /**\n * Hide the tab item from the native tabbar. When the hidden tab is selected,\n * native platform constraints may keep it visible until another tab is\n * selected.\n */\n hidden?: boolean;\n\n /**\n * Optional tab role. On floating tabbars, `search` (or `prominent` when\n * available) becomes a detached trailing circular action beside the capsule\n * — including the iOS 26+ system Liquid Glass tab bar, Android floating\n * layout, and the custom floating capsule path. Only one detached trailing\n * role is used; if multiple tabs set `search` or `prominent`, the last one\n * wins. Curve-shaped bars ignore `role` and keep using the included center\n * action. Defaults to `normal`.\n */\n role?: NativeNavigationTabRole;\n}\n\n/**\n * Native tabbar layout and background shape options.\n */\nexport interface NativeNavigationTabbarStyle {\n /**\n * `floating` keeps the capsule tabbar. On iOS 26+ this uses the\n * system-owned Liquid Glass `UITabBarController` unless a custom capsule\n * path is required; earlier iOS and the custom capsule path use\n * `UIGlassEffect` on iOS 26+ (blur material fallback otherwise). `curve`\n * draws a full-width bar with an included center action.\n */\n shape?: NativeNavigationTabbarShape;\n\n /**\n * Bar height in native points/dp, not including the system bottom inset.\n * Curve defaults to `49` on iOS (83pt once the home indicator is added) and\n * `80` on Android (Material navigation bar). Floating defaults to `64`.\n * Pass `height` to override either platform.\n */\n height?: number;\n\n /**\n * Horizontal margin in native points/dp. Defaults to `24` for `floating` and\n * `0` for `curve`.\n */\n horizontalMargin?: number;\n\n /**\n * Maximum tabbar width in native points/dp. Defaults to `430` for `floating`;\n * `curve` uses the available width unless this is set.\n */\n maxWidth?: number;\n\n /**\n * Bottom gap above the platform safe area in native points/dp. Defaults to\n * `10` for `floating` and `0` for `curve`.\n */\n bottomGap?: number;\n\n /**\n * Background corner radius in native points/dp. Defaults to a capsule radius\n * for `floating` and `0` for `curve`.\n */\n cornerRadius?: number;\n\n /**\n * Tab id promoted into the included center button for `curve`. Defaults to\n * the middle tab.\n */\n centerItemId?: string;\n\n /**\n * Included center button diameter in native points/dp. Defaults to `56`.\n */\n centerButtonDiameter?: number;\n\n /**\n * Distance from the top of the center button to the top edge of the bar in\n * native points/dp. Defaults to half of `centerButtonDiameter`.\n */\n centerButtonLift?: number;\n\n /**\n * Included center button color. Defaults to the active tint color.\n */\n centerButtonColor?: string;\n\n /**\n * Included center button icon color. Defaults to white.\n */\n centerButtonIconColor?: string;\n}\n\n/**\n * Native tabbar state.\n */\nexport interface NativeNavigationTabbarOptions {\n /**\n * Hide the native tabbar.\n */\n hidden?: boolean;\n\n /**\n * Tab definitions.\n */\n tabs?: NativeNavigationTab[];\n\n /**\n * Currently selected tab id.\n */\n selectedId?: string;\n\n /**\n * Show text labels. Defaults to `true`.\n */\n labels?: boolean;\n\n /**\n * Native label visibility mode. Overrides `labels` when provided.\n */\n labelVisibilityMode?: NativeNavigationTabLabelVisibilityMode;\n\n /**\n * Show icons. Defaults to `true`.\n */\n icons?: boolean;\n\n /**\n * Tabbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * iOS blur/material effect for the tabbar background when glass is not\n * available.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this tabbar update. On Android 12+, floating tabbars default to\n * `liquidGlass` (live blur plus tint) when this field is omitted. Pass\n * `effect: 'none'` for a flat translucent fill without blur.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Opt into the iOS 26 Liquid Glass tint workaround that renders active and\n * inactive tab items into baked images. This can affect badge positioning\n * and icon sizing, so it is disabled by default.\n */\n experimentalBakedTintColors?: boolean;\n\n /**\n * Keep the iOS scroll-edge tabbar appearance from becoming transparent.\n * Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option.\n * Defaults to `false`.\n */\n disableTransparentOnScrollEdge?: boolean;\n\n /**\n * Disable the Android active tab indicator.\n */\n disableIndicator?: boolean;\n\n /**\n * Active tab indicator color on Android. `colors.indicator` is also\n * supported.\n */\n indicatorColor?: string;\n\n /**\n * Tab press ripple color on Android. `colors.ripple` is also supported.\n */\n rippleColor?: string;\n\n /**\n * Badge background color. `colors.badgeBackground` is also supported.\n */\n badgeBackgroundColor?: string;\n\n /**\n * Badge text color. `colors.badgeText` is also supported.\n */\n badgeTextColor?: string;\n\n /**\n * Optional native tabbar layout and shape customization.\n */\n style?: NativeNavigationTabbarStyle;\n\n /**\n * Animate native tabbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * Insets exposed to web content.\n */\nexport interface NativeNavigationInsets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n navbarHeight: number;\n tabbarHeight: number;\n}\n\n/**\n * Returned by methods that may change safe content bounds.\n */\nexport interface NativeNavigationInsetsResult {\n insets: NativeNavigationInsets;\n}\n\n/**\n * Begin a native transition transaction before JS changes route content.\n */\nexport interface NativeNavigationBeginTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions. Use viewport coordinates such as\n * those returned by `Element.getBoundingClientRect()`.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Finish a native transition transaction after JS has changed route content.\n */\nexport interface NativeNavigationFinishTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions when no active source was recorded.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Native transition result.\n */\nexport interface NativeNavigationTransitionResult {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Plugin version payload.\n */\nexport interface PluginVersionResult {\n /**\n * Version identifier returned by the platform implementation.\n */\n version: string;\n}\n\nexport interface NativeNavigationBackEvent {\n source: 'navbar';\n}\n\nexport interface NativeNavigationBarItemTapEvent {\n id: string;\n title?: string;\n placement: 'left' | 'right';\n}\n\nexport interface NativeNavigationTabSelectEvent {\n id: string;\n index: number;\n title?: string;\n}\n\nexport interface NativeNavigationSafeAreaChangedEvent {\n insets: NativeNavigationInsets;\n}\n\nexport interface NativeNavigationTransitionEvent {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Framework-agnostic native navigation chrome API.\n */\nexport interface NativeNavigationPlugin {\n /**\n * Configure the native chrome host and content inset behavior.\n */\n configure(options?: NativeNavigationConfigureOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native navbar.\n */\n setNavbar(options: NativeNavigationNavbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native tabbar.\n */\n setTabbar(options: NativeNavigationTabbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Capture the current WebView and prepare a native transition.\n */\n beginTransition(options?: NativeNavigationBeginTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Animate from the captured WebView snapshot to the current live WebView.\n */\n finishTransition(options?: NativeNavigationFinishTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Returns the platform implementation version marker.\n */\n getPluginVersion(): Promise<PluginVersionResult>;\n\n addListener(\n eventName: 'navbarBack',\n listenerFunc: (event: NativeNavigationBackEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'navbarItemTap',\n listenerFunc: (event: NativeNavigationBarItemTapEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'tabSelect',\n listenerFunc: (event: NativeNavigationTabSelectEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'safeAreaChanged',\n listenerFunc: (event: NativeNavigationSafeAreaChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionStart',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionEnd',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
@@ -58,6 +58,9 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
58
58
  private var tabbarHeight: CGFloat = NativeNavigationTabbarStyleConfig().totalHeight
59
59
  private var navbarVisible = false
60
60
  private var tabbarVisible = false
61
+ private var tabbarSoftHidden = false
62
+ private var tabbarSkipImmediateSystemReveal = false
63
+ private var floatingTabBarWantsHidden = false
61
64
  private var contentInsetMode = "css"
62
65
  private var isEnabled = true
63
66
  private var defaultTransitionDuration: TimeInterval = 0.35
@@ -203,15 +206,36 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
203
206
  }
204
207
 
205
208
  let hidden = call.getBool("hidden", false)
209
+ let animated = call.getBool("animated", false)
206
210
  self.tabbarVisible = !hidden
207
211
 
208
212
  guard !hidden else {
209
- self.hideTabBarChrome()
213
+ self.hideTabBarChrome(animated: animated)
210
214
  self.updateInsetsAndNotify()
211
215
  call.resolve(self.insetsResult())
212
216
  return
213
217
  }
214
218
 
219
+ if self.tabbarSoftHidden {
220
+ self.tabbarSoftHidden = false
221
+ if animated {
222
+ if nativeNavigationPrefersAnimatedSystemTabbarVisibility(
223
+ animated: true,
224
+ usesSystemLiquidGlass: self.usesSystemLiquidGlass,
225
+ shape: self.tabbarStyle.shape == .curve ? "curve" : "floating"
226
+ ) {
227
+ self.tabbarSkipImmediateSystemReveal = true
228
+ self.setSystemTabBarHidden(false, animated: true, syncSubviewState: false)
229
+ } else {
230
+ self.setFloatingTabBarVisibility(hidden: false, animated: true)
231
+ }
232
+ } else if self.usesSystemLiquidGlass && self.tabbarStyle.shape != .curve {
233
+ self.setSystemTabBarHidden(false, animated: false, syncSubviewState: true)
234
+ } else {
235
+ self.setFloatingTabBarVisibility(hidden: false, animated: false)
236
+ }
237
+ }
238
+
215
239
  self.tabbarStyle = self.makeTabbarStyle(from: call)
216
240
  self.tabbarHeight = self.tabbarStyle.totalHeight
217
241
 
@@ -241,6 +265,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
241
265
  self.applyTabBarAppearance(tabBar: tabBar, options: call)
242
266
  if items.isEmpty {
243
267
  self.tabbarVisible = false
268
+ self.tabbarSkipImmediateSystemReveal = false
244
269
  self.hideSystemTabBarChromeCompletely()
245
270
  } else {
246
271
  self.showTabBarChrome(tabBar)
@@ -904,18 +929,22 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
904
929
  }
905
930
  }
906
931
 
907
- private func setSystemTabBarHidden(_ hidden: Bool) {
932
+ private func setSystemTabBarHidden(_ hidden: Bool, animated: Bool = false, syncSubviewState: Bool = true) {
908
933
  guard let tabBarController = tabBarController else {
909
934
  return
910
935
  }
911
936
 
912
937
  let tabBar = tabBarController.tabBar
913
938
  if #available(iOS 18.0, *) {
914
- tabBarController.setTabBarHidden(hidden, animated: false)
939
+ tabBarController.setTabBarHidden(hidden, animated: animated)
915
940
  } else {
916
941
  tabBar.isHidden = hidden
917
942
  }
918
943
 
944
+ guard syncSubviewState else {
945
+ return
946
+ }
947
+
919
948
  if hidden {
920
949
  setTabBarBackgroundSubviewsHidden(true, on: tabBar)
921
950
  } else {
@@ -943,7 +972,24 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
943
972
  floatingTabBar?.isHidden = true
944
973
  }
945
974
 
946
- private func hideTabBarChrome() {
975
+ private func hideTabBarChrome(animated: Bool = false) {
976
+ if nativeNavigationPrefersAnimatedSystemTabbarVisibility(
977
+ animated: animated,
978
+ usesSystemLiquidGlass: usesSystemLiquidGlass,
979
+ shape: tabbarStyle.shape == .curve ? "curve" : "floating"
980
+ ) {
981
+ tabbarSoftHidden = true
982
+ setSystemTabBarHidden(true, animated: true, syncSubviewState: false)
983
+ return
984
+ }
985
+
986
+ if animated {
987
+ tabbarSoftHidden = true
988
+ setFloatingTabBarVisibility(hidden: true, animated: true)
989
+ return
990
+ }
991
+
992
+ tabbarSoftHidden = false
947
993
  if usesSystemLiquidGlass && tabbarStyle.shape != .curve {
948
994
  hideSystemTabBarChromeCompletely()
949
995
  } else {
@@ -953,12 +999,51 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
953
999
  }
954
1000
  }
955
1001
 
1002
+ private func setFloatingTabBarVisibility(hidden: Bool, animated: Bool) {
1003
+ floatingTabBarWantsHidden = hidden
1004
+ let targets = [tabContainer, floatingTabBar].compactMap { $0 }
1005
+ guard !targets.isEmpty else {
1006
+ return
1007
+ }
1008
+
1009
+ if !animated {
1010
+ targets.forEach { view in
1011
+ view.isHidden = hidden
1012
+ view.alpha = hidden ? 0 : 1
1013
+ }
1014
+ return
1015
+ }
1016
+
1017
+ if hidden {
1018
+ UIView.animate(withDuration: 0.2, animations: {
1019
+ targets.forEach { $0.alpha = 0 }
1020
+ }, completion: { finished in
1021
+ guard finished, self.floatingTabBarWantsHidden else {
1022
+ return
1023
+ }
1024
+ targets.forEach { $0.isHidden = true }
1025
+ })
1026
+ return
1027
+ }
1028
+
1029
+ targets.forEach { view in
1030
+ view.isHidden = false
1031
+ view.alpha = 0
1032
+ }
1033
+ UIView.animate(withDuration: 0.2) {
1034
+ targets.forEach { $0.alpha = 1 }
1035
+ }
1036
+ }
1037
+
956
1038
  private func showTabBarChrome(_ tabBar: UITabBar) {
957
1039
  tabContainer?.isHidden = true
958
1040
  floatingTabBar?.isHidden = true
959
1041
  tabBarController?.view.isHidden = false
960
1042
  if usesSystemLiquidGlass && tabbarStyle.shape != .curve {
961
- setSystemTabBarHidden(false)
1043
+ if !tabbarSkipImmediateSystemReveal {
1044
+ setSystemTabBarHidden(false)
1045
+ }
1046
+ tabbarSkipImmediateSystemReveal = false
962
1047
  liftWebViewOverlaysAboveSystemTabs()
963
1048
  hostWebViewInSelectedSystemTab()
964
1049
  } else {
@@ -2780,6 +2865,14 @@ func nativeNavigationUsesStationaryTransitionCrossfade(direction: String) -> Boo
2780
2865
  direction == "tab" || direction == "root" || direction == "none"
2781
2866
  }
2782
2867
 
2868
+ func nativeNavigationPrefersAnimatedSystemTabbarVisibility(
2869
+ animated: Bool,
2870
+ usesSystemLiquidGlass: Bool,
2871
+ shape: String
2872
+ ) -> Bool {
2873
+ animated && usesSystemLiquidGlass && shape != "curve"
2874
+ }
2875
+
2783
2876
  private func nativeNavigationNeedsTransitionSurface(_ color: UIColor?) -> Bool {
2784
2877
  guard let color else {
2785
2878
  return true
@@ -97,4 +97,28 @@ class NativeNavigationTests: XCTestCase {
97
97
  XCTAssertFalse(nativeNavigationUsesStationaryTransitionCrossfade(direction: "forward"))
98
98
  XCTAssertFalse(nativeNavigationUsesStationaryTransitionCrossfade(direction: "back"))
99
99
  }
100
+
101
+ func testAnimatedSystemTabbarVisibilityUsesSoftHide() {
102
+ XCTAssertTrue(
103
+ nativeNavigationPrefersAnimatedSystemTabbarVisibility(
104
+ animated: true,
105
+ usesSystemLiquidGlass: true,
106
+ shape: "floating"
107
+ )
108
+ )
109
+ XCTAssertFalse(
110
+ nativeNavigationPrefersAnimatedSystemTabbarVisibility(
111
+ animated: false,
112
+ usesSystemLiquidGlass: true,
113
+ shape: "floating"
114
+ )
115
+ )
116
+ XCTAssertFalse(
117
+ nativeNavigationPrefersAnimatedSystemTabbarVisibility(
118
+ animated: true,
119
+ usesSystemLiquidGlass: true,
120
+ shape: "curve"
121
+ )
122
+ )
123
+ }
100
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.4.4",
3
+ "version": "8.5.0",
4
4
  "description": "Capacitor plugin for native navbar, tabbar, safe-area handling, and WebView snapshot transitions.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",