@capgo/capacitor-native-navigation 8.2.9 → 8.3.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
@@ -791,34 +791,35 @@ Native tabbar state.
791
791
 
792
792
  A native tab item.
793
793
 
794
- | Prop | Type | Description |
795
- | ------------------ | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
796
- | **`id`** | <code>string</code> | Stable tab id returned in `tabSelect`. |
797
- | **`title`** | <code>string</code> | Visible tab label. |
798
- | **`icon`** | <code><a href="#nativenavigationicon">NativeNavigationIcon</a></code> | Native icon descriptor. |
799
- | **`selectedIcon`** | <code><a href="#nativenavigationicon">NativeNavigationIcon</a></code> | Optional selected-state icon. |
800
- | **`badge`** | <code>string \| number</code> | Optional badge. Numeric badges are supported on both platforms; text badge support depends on platform capabilities. |
801
- | **`enabled`** | <code>boolean</code> | Whether the tab is enabled. Defaults to `true`. |
802
- | **`hidden`** | <code>boolean</code> | Hide the tab item from the native tabbar. When the hidden tab is selected, native platform constraints may keep it visible until another tab is selected. |
794
+ | Prop | Type | Description |
795
+ | ------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
796
+ | **`id`** | <code>string</code> | Stable tab id returned in `tabSelect`. |
797
+ | **`title`** | <code>string</code> | Visible tab label. |
798
+ | **`icon`** | <code><a href="#nativenavigationicon">NativeNavigationIcon</a></code> | Native icon descriptor. |
799
+ | **`selectedIcon`** | <code><a href="#nativenavigationicon">NativeNavigationIcon</a></code> | Optional selected-state icon. |
800
+ | **`badge`** | <code>string \| number</code> | Optional badge. Numeric badges are supported on both platforms; text badge support depends on platform capabilities. |
801
+ | **`enabled`** | <code>boolean</code> | Whether the tab is enabled. Defaults to `true`. |
802
+ | **`hidden`** | <code>boolean</code> | Hide the tab item from the native tabbar. When the hidden tab is selected, native platform constraints may keep it visible until another tab is selected. |
803
+ | **`role`** | <code><a href="#nativenavigationtabrole">NativeNavigationTabRole</a></code> | Optional tab role. On floating tabbars, `search` (or `prominent` when available) becomes a detached trailing circular action beside the capsule — including the iOS 26+ system Liquid Glass tab bar, Android floating layout, and the custom floating capsule path. Only one detached trailing role is used; if multiple tabs set `search` or `prominent`, the last one wins. Curve-shaped bars ignore `role` and keep using the included center action. Defaults to `normal`. |
803
804
 
804
805
 
805
806
  #### NativeNavigationTabbarStyle
806
807
 
807
808
  Native tabbar layout and background shape options.
808
809
 
809
- | Prop | Type | Description |
810
- | --------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
811
- | **`shape`** | <code><a href="#nativenavigationtabbarshape">NativeNavigationTabbarShape</a></code> | `floating` keeps the existing capsule tabbar. `curve` draws a full-width bar with an included center action. |
812
- | **`height`** | <code>number</code> | Bar height in native points/dp. Defaults to `64` for `floating` and `76` for `curve`. |
813
- | **`horizontalMargin`** | <code>number</code> | Horizontal margin in native points/dp. Defaults to `24` for `floating` and `0` for `curve`. |
814
- | **`maxWidth`** | <code>number</code> | Maximum tabbar width in native points/dp. Defaults to `430` for `floating`; `curve` uses the available width unless this is set. |
815
- | **`bottomGap`** | <code>number</code> | Bottom gap above the platform safe area in native points/dp. Defaults to `10` for `floating` and `0` for `curve`. |
816
- | **`cornerRadius`** | <code>number</code> | Background corner radius in native points/dp. Defaults to a capsule radius for `floating` and `0` for `curve`. |
817
- | **`centerItemId`** | <code>string</code> | Tab id promoted into the included center button for `curve`. Defaults to the middle tab. |
818
- | **`centerButtonDiameter`** | <code>number</code> | Included center button diameter in native points/dp. Defaults to `56`. |
819
- | **`centerButtonLift`** | <code>number</code> | Distance from the top of the center button to the top edge of the bar in native points/dp. Defaults to half of `centerButtonDiameter`. |
820
- | **`centerButtonColor`** | <code>string</code> | Included center button color. Defaults to the active tint color. |
821
- | **`centerButtonIconColor`** | <code>string</code> | Included center button icon color. Defaults to white. |
810
+ | Prop | Type | Description |
811
+ | --------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
812
+ | **`shape`** | <code><a href="#nativenavigationtabbarshape">NativeNavigationTabbarShape</a></code> | `floating` keeps the capsule tabbar. On iOS 26+ this uses the system-owned Liquid Glass `UITabBarController` unless a custom capsule path is required; earlier iOS and the custom capsule path use `UIGlassEffect` on iOS 26+ (blur material fallback otherwise). `curve` draws a full-width bar with an included center action. |
813
+ | **`height`** | <code>number</code> | Bar height in native points/dp. Defaults to `64` for `floating` and `76` for `curve`. |
814
+ | **`horizontalMargin`** | <code>number</code> | Horizontal margin in native points/dp. Defaults to `24` for `floating` and `0` for `curve`. |
815
+ | **`maxWidth`** | <code>number</code> | Maximum tabbar width in native points/dp. Defaults to `430` for `floating`; `curve` uses the available width unless this is set. |
816
+ | **`bottomGap`** | <code>number</code> | Bottom gap above the platform safe area in native points/dp. Defaults to `10` for `floating` and `0` for `curve`. |
817
+ | **`cornerRadius`** | <code>number</code> | Background corner radius in native points/dp. Defaults to a capsule radius for `floating` and `0` for `curve`. |
818
+ | **`centerItemId`** | <code>string</code> | Tab id promoted into the included center button for `curve`. Defaults to the middle tab. |
819
+ | **`centerButtonDiameter`** | <code>number</code> | Included center button diameter in native points/dp. Defaults to `56`. |
820
+ | **`centerButtonLift`** | <code>number</code> | Distance from the top of the center button to the top edge of the bar in native points/dp. Defaults to half of `centerButtonDiameter`. |
821
+ | **`centerButtonColor`** | <code>string</code> | Included center button color. Defaults to the active tint color. |
822
+ | **`centerButtonIconColor`** | <code>string</code> | Included center button icon color. Defaults to white. |
822
823
 
823
824
 
824
825
  #### NativeNavigationTransitionResult
@@ -960,6 +961,19 @@ Native material/blur effect preference.
960
961
  <code>'none' | 'systemDefault' | 'extraLight' | 'light' | 'dark' | 'regular' | 'prominent' | 'systemUltraThinMaterial' | 'systemThinMaterial' | 'systemMaterial' | 'systemThickMaterial' | 'systemChromeMaterial' | 'systemUltraThinMaterialLight' | 'systemThinMaterialLight' | 'systemMaterialLight' | 'systemThickMaterialLight' | 'systemChromeMaterialLight' | 'systemUltraThinMaterialDark' | 'systemThinMaterialDark' | 'systemMaterialDark' | 'systemThickMaterialDark' | 'systemChromeMaterialDark'</code>
961
962
 
962
963
 
964
+ #### NativeNavigationTabRole
965
+
966
+ Native tab role for Liquid Glass tab bars.
967
+
968
+ `search` (and `prominent` when the OS supports it) renders as a detached
969
+ trailing circular action beside the floating tab capsule — the Apple News /
970
+ Photos pattern. Only one detached trailing role is used; if multiple tabs
971
+ set `search` or `prominent`, the last one wins. Curve-shaped bars ignore
972
+ `role` and keep using the included center action instead.
973
+
974
+ <code>'normal' | 'search' | 'prominent'</code>
975
+
976
+
963
977
  #### NativeNavigationTabLabelVisibilityMode
964
978
 
965
979
  Native tab label visibility behavior.
@@ -256,14 +256,39 @@ public class NativeNavigationPlugin extends Plugin {
256
256
  continue;
257
257
  }
258
258
 
259
- int visibleIndex = tabItems.size();
260
259
  String title = tab.optString("title", "");
261
260
  Drawable icon = icons ? iconFrom(tab.optJSONObject("icon")) : new ColorDrawable(Color.TRANSPARENT);
262
261
  Drawable selectedIcon = icons ? iconFrom(tab.optJSONObject("selectedIcon")) : null;
263
262
  String badge = tab.has("badge") ? String.valueOf(tab.opt("badge")) : null;
264
- tabItems.add(new NativeTabItem(id, title, icon, selectedIcon, badge, tab.optBoolean("enabled", true), sourceIndex));
265
- if (id.equals(selectedId)) {
266
- selectedTabIndex = visibleIndex;
263
+ String role = tab.optString("role", "normal");
264
+ boolean detachedTrailing = "search".equalsIgnoreCase(role) || "prominent".equalsIgnoreCase(role);
265
+ tabItems.add(
266
+ new NativeTabItem(id, title, icon, selectedIcon, badge, tab.optBoolean("enabled", true), detachedTrailing, sourceIndex)
267
+ );
268
+ }
269
+
270
+ applyTabbarColors(call, colors);
271
+ tabbarStyle = makeTabbarStyle(call.getObject("style", new JSObject()));
272
+
273
+ // Keep at most one detached trailing action for floating bars.
274
+ // Curve bars ignore role so tab order / center selection stay stable.
275
+ if (!tabbarStyle.isCurve()) {
276
+ NativeTabItem trailingItem = null;
277
+ for (int index = tabItems.size() - 1; index >= 0; index--) {
278
+ if (tabItems.get(index).detachedTrailing) {
279
+ trailingItem = tabItems.remove(index);
280
+ break;
281
+ }
282
+ }
283
+ if (trailingItem != null) {
284
+ tabItems.removeIf((item) -> item.detachedTrailing);
285
+ tabItems.add(trailingItem);
286
+ }
287
+ }
288
+ for (int index = 0; index < tabItems.size(); index++) {
289
+ if (tabItems.get(index).id.equals(selectedId)) {
290
+ selectedTabIndex = index;
291
+ break;
267
292
  }
268
293
  }
269
294
 
@@ -283,8 +308,6 @@ public class NativeNavigationPlugin extends Plugin {
283
308
  selectedTabIndex = 0;
284
309
  }
285
310
 
286
- applyTabbarColors(call, colors);
287
- tabbarStyle = makeTabbarStyle(call.getObject("style", new JSObject()));
288
311
  applyTabbarBackground(centerTabIndex());
289
312
  renderTabbarItems(labelVisibilityMode, icons);
290
313
  if (tabbarContainer != null) {
@@ -713,8 +736,14 @@ public class NativeNavigationPlugin extends Plugin {
713
736
  final int itemIndex = index;
714
737
  NativeTabItem item = tabItems.get(index);
715
738
  boolean selected = itemIndex == selectedTabIndex;
716
- boolean showLabel = shouldShowTabLabel(labelVisibilityMode, selected);
739
+ boolean detachedTrailing = item.detachedTrailing && !tabbarStyle.isCurve();
740
+ boolean showLabel = detachedTrailing
741
+ ? !icons && shouldShowTabLabel(labelVisibilityMode, selected)
742
+ : shouldShowTabLabel(labelVisibilityMode, selected);
717
743
  FrameLayout button = makeTabButton(item, selected, showLabel, icons, itemIndex == centerIndex);
744
+ if (detachedTrailing) {
745
+ button.setTag("detachedTrailing");
746
+ }
718
747
  button.setSelected(selected);
719
748
  button.setEnabled(item.enabled);
720
749
  button.setAlpha(item.enabled ? 1f : 0.38f);
@@ -984,15 +1013,26 @@ public class NativeNavigationPlugin extends Plugin {
984
1013
  final Drawable selectedIcon;
985
1014
  final String badge;
986
1015
  final boolean enabled;
1016
+ final boolean detachedTrailing;
987
1017
  final int sourceIndex;
988
1018
 
989
- NativeTabItem(String id, String title, Drawable icon, Drawable selectedIcon, String badge, boolean enabled, int sourceIndex) {
1019
+ NativeTabItem(
1020
+ String id,
1021
+ String title,
1022
+ Drawable icon,
1023
+ Drawable selectedIcon,
1024
+ String badge,
1025
+ boolean enabled,
1026
+ boolean detachedTrailing,
1027
+ int sourceIndex
1028
+ ) {
990
1029
  this.id = id;
991
1030
  this.title = title;
992
1031
  this.icon = icon;
993
1032
  this.selectedIcon = selectedIcon;
994
1033
  this.badge = badge;
995
1034
  this.enabled = enabled;
1035
+ this.detachedTrailing = detachedTrailing;
996
1036
  this.sourceIndex = sourceIndex;
997
1037
  }
998
1038
  }
@@ -1098,6 +1138,17 @@ public class NativeNavigationPlugin extends Plugin {
1098
1138
  return;
1099
1139
  }
1100
1140
 
1141
+ int trailingIndex = detachedTrailingIndex();
1142
+ if (trailingIndex >= 0) {
1143
+ int trailingDiameter = dp(style.height);
1144
+ int capsuleWidth = Math.max(0, width - trailingDiameter - dp(10));
1145
+ measureRange(0, trailingIndex, capsuleWidth, height);
1146
+ measureChildExact(getChildAt(trailingIndex), trailingDiameter, trailingDiameter);
1147
+ measureRange(trailingIndex + 1, getChildCount(), 0, height);
1148
+ setMeasuredDimension(width, height);
1149
+ return;
1150
+ }
1151
+
1101
1152
  int count = Math.max(getChildCount(), 1);
1102
1153
  int childWidth = width / count;
1103
1154
  for (int index = 0; index < getChildCount(); index++) {
@@ -1125,6 +1176,17 @@ public class NativeNavigationPlugin extends Plugin {
1125
1176
  return;
1126
1177
  }
1127
1178
 
1179
+ int trailingIndex = detachedTrailingIndex();
1180
+ if (trailingIndex >= 0) {
1181
+ int trailingDiameter = dp(style.height);
1182
+ int capsuleWidth = Math.max(0, width - trailingDiameter - dp(10));
1183
+ View trailing = getChildAt(trailingIndex);
1184
+ int trailingTop = Math.max(0, (height - trailingDiameter) / 2);
1185
+ trailing.layout(width - trailingDiameter, trailingTop, width, trailingTop + trailingDiameter);
1186
+ layoutRange(0, trailingIndex, 0, 0, capsuleWidth, height);
1187
+ return;
1188
+ }
1189
+
1128
1190
  layoutRange(0, getChildCount(), 0, 0, width, height);
1129
1191
  }
1130
1192
 
@@ -1132,6 +1194,27 @@ public class NativeNavigationPlugin extends Plugin {
1132
1194
  return style.isCurve() && centerIndex >= 0 && centerIndex < getChildCount();
1133
1195
  }
1134
1196
 
1197
+ private int detachedTrailingIndex() {
1198
+ if (style.isCurve()) {
1199
+ return -1;
1200
+ }
1201
+ for (int index = 0; index < getChildCount(); index++) {
1202
+ Object tag = getChildAt(index).getTag();
1203
+ if ("detachedTrailing".equals(tag)) {
1204
+ return index;
1205
+ }
1206
+ }
1207
+ return -1;
1208
+ }
1209
+
1210
+ private int capsuleWidth(int width) {
1211
+ int trailingIndex = detachedTrailingIndex();
1212
+ if (trailingIndex < 0) {
1213
+ return width;
1214
+ }
1215
+ return Math.max(0, width - dp(style.height) - dp(10));
1216
+ }
1217
+
1135
1218
  private void measureRange(int start, int end, int width, int height) {
1136
1219
  int count = Math.max(0, end - start);
1137
1220
  if (count == 0) {
@@ -1166,7 +1249,20 @@ public class NativeNavigationPlugin extends Plugin {
1166
1249
  Path path = new Path();
1167
1250
  if (!style.isCurve()) {
1168
1251
  float radius = dp(style.cornerRadius);
1169
- path.addRoundRect(new RectF(0, 0, width, height), radius, radius, Path.Direction.CW);
1252
+ int capsuleWidth = capsuleWidth(width);
1253
+ path.addRoundRect(new RectF(0, 0, capsuleWidth, height), radius, radius, Path.Direction.CW);
1254
+ int trailingIndex = detachedTrailingIndex();
1255
+ if (trailingIndex >= 0) {
1256
+ float diameter = dp(style.height);
1257
+ float left = width - diameter;
1258
+ float top = (height - diameter) / 2f;
1259
+ path.addRoundRect(
1260
+ new RectF(left, top, left + diameter, top + diameter),
1261
+ diameter / 2f,
1262
+ diameter / 2f,
1263
+ Path.Direction.CW
1264
+ );
1265
+ }
1170
1266
  return path;
1171
1267
  }
1172
1268
  float barTop = dp(style.barTop());
@@ -1707,7 +1803,9 @@ public class NativeNavigationPlugin extends Plugin {
1707
1803
  int rootWidth = root.getWidth() > 0 ? root.getWidth() : Resources.getSystem().getDisplayMetrics().widthPixels;
1708
1804
  int availableWidth = Math.max(0, rootWidth - dp(tabbarStyle.horizontalMargin) * 2);
1709
1805
  int maxWidth = tabbarStyle.maxWidth > 0 ? dp(tabbarStyle.maxWidth) : availableWidth;
1710
- int tabbarWidth = Math.min(availableWidth, maxWidth);
1806
+ boolean hasDetachedTrailing = !tabbarStyle.isCurve() && tabItems.stream().anyMatch((item) -> item.detachedTrailing);
1807
+ int trailingExtra = hasDetachedTrailing ? dp(tabbarStyle.height) + dp(10) : 0;
1808
+ int tabbarWidth = Math.min(availableWidth, maxWidth + trailingExtra);
1711
1809
  FrameLayout.LayoutParams tabbarContainerParams = new FrameLayout.LayoutParams(
1712
1810
  tabbarWidth,
1713
1811
  tabbarHeight,
package/dist/docs.json CHANGED
@@ -923,6 +923,15 @@
923
923
  "docs": "Hide the tab item from the native tabbar. When the hidden tab is selected,\nnative platform constraints may keep it visible until another tab is\nselected.",
924
924
  "complexTypes": [],
925
925
  "type": "boolean | undefined"
926
+ },
927
+ {
928
+ "name": "role",
929
+ "tags": [],
930
+ "docs": "Optional tab role. On floating tabbars, `search` (or `prominent` when\navailable) becomes a detached trailing circular action beside the capsule\n— including the iOS 26+ system Liquid Glass tab bar, Android floating\nlayout, and the custom floating capsule path. Only one detached trailing\nrole is used; if multiple tabs set `search` or `prominent`, the last one\nwins. Curve-shaped bars ignore `role` and keep using the included center\naction. Defaults to `normal`.",
931
+ "complexTypes": [
932
+ "NativeNavigationTabRole"
933
+ ],
934
+ "type": "NativeNavigationTabRole"
926
935
  }
927
936
  ]
928
937
  },
@@ -936,7 +945,7 @@
936
945
  {
937
946
  "name": "shape",
938
947
  "tags": [],
939
- "docs": "`floating` keeps the existing capsule tabbar. `curve` draws a full-width\nbar with an included center action.",
948
+ "docs": "`floating` keeps the capsule tabbar. On iOS 26+ this uses the\nsystem-owned Liquid Glass `UITabBarController` unless a custom capsule\npath is required; earlier iOS and the custom capsule path use\n`UIGlassEffect` on iOS 26+ (blur material fallback otherwise). `curve`\ndraws a full-width bar with an included center action.",
940
949
  "complexTypes": [
941
950
  "NativeNavigationTabbarShape"
942
951
  ],
@@ -1502,6 +1511,25 @@
1502
1511
  }
1503
1512
  ]
1504
1513
  },
1514
+ {
1515
+ "name": "NativeNavigationTabRole",
1516
+ "slug": "nativenavigationtabrole",
1517
+ "docs": "Native tab role for Liquid Glass tab bars.\n\n`search` (and `prominent` when the OS supports it) renders as a detached\ntrailing circular action beside the floating tab capsule — the Apple News /\nPhotos pattern. Only one detached trailing role is used; if multiple tabs\nset `search` or `prominent`, the last one wins. Curve-shaped bars ignore\n`role` and keep using the included center action instead.",
1518
+ "types": [
1519
+ {
1520
+ "text": "'normal'",
1521
+ "complexTypes": []
1522
+ },
1523
+ {
1524
+ "text": "'search'",
1525
+ "complexTypes": []
1526
+ },
1527
+ {
1528
+ "text": "'prominent'",
1529
+ "complexTypes": []
1530
+ }
1531
+ ]
1532
+ },
1505
1533
  {
1506
1534
  "name": "NativeNavigationTabLabelVisibilityMode",
1507
1535
  "slug": "nativenavigationtablabelvisibilitymode",
@@ -56,6 +56,16 @@ export interface NativeNavigationRect {
56
56
  * Native tabbar background shape.
57
57
  */
58
58
  export type NativeNavigationTabbarShape = 'floating' | 'curve';
59
+ /**
60
+ * Native tab role for Liquid Glass tab bars.
61
+ *
62
+ * `search` (and `prominent` when the OS supports it) renders as a detached
63
+ * trailing circular action beside the floating tab capsule — the Apple News /
64
+ * Photos pattern. Only one detached trailing role is used; if multiple tabs
65
+ * set `search` or `prominent`, the last one wins. Curve-shaped bars ignore
66
+ * `role` and keep using the included center action instead.
67
+ */
68
+ export type NativeNavigationTabRole = 'normal' | 'search' | 'prominent';
59
69
  /**
60
70
  * A serializable icon descriptor. Framework nodes are intentionally not accepted
61
71
  * because icons are rendered by native UI.
@@ -317,14 +327,27 @@ export interface NativeNavigationTab {
317
327
  * selected.
318
328
  */
319
329
  hidden?: boolean;
330
+ /**
331
+ * Optional tab role. On floating tabbars, `search` (or `prominent` when
332
+ * available) becomes a detached trailing circular action beside the capsule
333
+ * — including the iOS 26+ system Liquid Glass tab bar, Android floating
334
+ * layout, and the custom floating capsule path. Only one detached trailing
335
+ * role is used; if multiple tabs set `search` or `prominent`, the last one
336
+ * wins. Curve-shaped bars ignore `role` and keep using the included center
337
+ * action. Defaults to `normal`.
338
+ */
339
+ role?: NativeNavigationTabRole;
320
340
  }
321
341
  /**
322
342
  * Native tabbar layout and background shape options.
323
343
  */
324
344
  export interface NativeNavigationTabbarStyle {
325
345
  /**
326
- * `floating` keeps the existing capsule tabbar. `curve` draws a full-width
327
- * bar with an included center action.
346
+ * `floating` keeps the capsule tabbar. On iOS 26+ this uses the
347
+ * system-owned Liquid Glass `UITabBarController` unless a custom capsule
348
+ * path is required; earlier iOS and the custom capsule path use
349
+ * `UIGlassEffect` on iOS 26+ (blur material fallback otherwise). `curve`
350
+ * draws a full-width bar with an included center action.
328
351
  */
329
352
  shape?: NativeNavigationTabbarShape;
330
353
  /**
@@ -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 * 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/**\n * Native tabbar layout and background shape options.\n */\nexport interface NativeNavigationTabbarStyle {\n /**\n * `floating` keeps the existing capsule tabbar. `curve` draws a full-width\n * bar with an included center action.\n */\n shape?: NativeNavigationTabbarShape;\n\n /**\n * Bar height in native points/dp. Defaults to `64` for `floating` and `76`\n * for `curve`.\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.\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. Defaults to `64` for `floating` and `76`\n * for `curve`.\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"]}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import Foundation
4
4
  import Capacitor
5
+ import ObjectiveC
5
6
  import UIKit
6
7
 
7
8
  private struct NativeNavigationTransitionContext {
@@ -41,6 +42,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
41
42
  private var navBar: UINavigationBar?
42
43
  private var tabContainer: UIView?
43
44
  private var tabEffectView: UIVisualEffectView?
45
+ private var trailingTabEffectView: UIVisualEffectView?
44
46
  private var tabBar: UITabBar?
45
47
  private var floatingTabBar: NativeNavigationFloatingTabBar?
46
48
  private var tabbarStyle = NativeNavigationTabbarStyleConfig()
@@ -726,13 +728,21 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
726
728
  container.layer.shadowRadius = 18
727
729
  container.layer.shadowOffset = CGSize(width: 0, height: 10)
728
730
 
729
- let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial))
731
+ let effectView = UIVisualEffectView(effect: liquidGlassEffect() ?? UIBlurEffect(style: .systemChromeMaterial))
730
732
  effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
731
733
  effectView.isUserInteractionEnabled = false
732
734
  effectView.clipsToBounds = true
733
735
  container.addSubview(effectView)
734
736
  self.tabEffectView = effectView
735
737
 
738
+ let trailingEffectView = UIVisualEffectView(effect: liquidGlassEffect() ?? UIBlurEffect(style: .systemChromeMaterial))
739
+ trailingEffectView.autoresizingMask = []
740
+ trailingEffectView.isUserInteractionEnabled = false
741
+ trailingEffectView.clipsToBounds = true
742
+ trailingEffectView.isHidden = true
743
+ container.addSubview(trailingEffectView)
744
+ self.trailingTabEffectView = trailingEffectView
745
+
736
746
  let bar = NativeNavigationFloatingTabBar()
737
747
  bar.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
738
748
  bar.clipsToBounds = false
@@ -1090,6 +1100,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1090
1100
  tabSelectedImages = []
1091
1101
  var selectedIndex: Int?
1092
1102
  var items: [UITabBarItem] = []
1103
+ var trailingEntry: (item: UITabBarItem, title: String?, image: UIImage?, selectedImage: UIImage?)?
1093
1104
 
1094
1105
  for (sourceIndex, tab) in tabs.enumerated() {
1095
1106
  let id = tab["id"] as? String ?? "tab-\(sourceIndex)"
@@ -1098,19 +1109,35 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1098
1109
  continue
1099
1110
  }
1100
1111
 
1101
- let visibleIndex = items.count
1102
1112
  let rawTitle = tab["title"] as? String ?? ""
1103
1113
  let title = tabTitle(
1104
1114
  rawTitle,
1105
1115
  id: id,
1106
- index: visibleIndex,
1116
+ index: items.count,
1107
1117
  selectedId: selectedId,
1108
1118
  labelVisibilityMode: labelVisibilityMode
1109
1119
  )
1110
1120
  let image = icons ? self.image(from: tab["icon"] as? [String: Any]) : nil
1111
1121
  let selectedImage = icons ? self.image(from: tab["selectedIcon"] as? [String: Any]) : nil
1112
- let item = UITabBarItem(title: title, image: image, selectedImage: selectedImage)
1113
- item.tag = sourceIndex
1122
+ let role = (tab["role"] as? String)?.lowercased()
1123
+ let item: UITabBarItem
1124
+ if role == "search" || role == "prominent" {
1125
+ // iOS 26+ Liquid Glass renders `.search` as a detached trailing
1126
+ // circular action beside the floating tab capsule. Keep the
1127
+ // system-item title empty so UIKit treats it as icon-only.
1128
+ item = UITabBarItem(tabBarSystemItem: .search, tag: sourceIndex)
1129
+ item.title = nil
1130
+ if let image = image {
1131
+ item.image = image
1132
+ }
1133
+ if let selectedImage = selectedImage {
1134
+ item.selectedImage = selectedImage
1135
+ }
1136
+ item.accessibilityLabel = rawTitle.isEmpty ? id : rawTitle
1137
+ } else {
1138
+ item = UITabBarItem(title: title, image: image, selectedImage: selectedImage)
1139
+ item.tag = sourceIndex
1140
+ }
1114
1141
  item.isEnabled = tab["enabled"] as? Bool ?? true
1115
1142
  if let badge = tab["badge"] {
1116
1143
  item.badgeValue = String(describing: badge)
@@ -1121,17 +1148,31 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1121
1148
  }
1122
1149
  tabIds[sourceIndex] = id
1123
1150
  tabTitles[sourceIndex] = rawTitle
1124
- tabDisplayTitles.append(title)
1125
- tabBaseImages.append(image)
1126
- tabSelectedImages.append(selectedImage ?? image)
1127
- if id == selectedId {
1128
- selectedIndex = visibleIndex
1151
+ if role == "search" || role == "prominent" {
1152
+ trailingEntry = (item, title, image, selectedImage ?? image)
1153
+ } else {
1154
+ items.append(item)
1155
+ tabDisplayTitles.append(title)
1156
+ tabBaseImages.append(image)
1157
+ tabSelectedImages.append(selectedImage ?? image)
1129
1158
  }
1130
- items.append(item)
1159
+ }
1160
+
1161
+ if let trailingEntry = trailingEntry {
1162
+ items.append(trailingEntry.item)
1163
+ tabDisplayTitles.append(trailingEntry.title)
1164
+ tabBaseImages.append(trailingEntry.image)
1165
+ tabSelectedImages.append(trailingEntry.selectedImage)
1166
+ }
1167
+ if let selectedId = selectedId {
1168
+ selectedIndex = items.firstIndex(where: { item in
1169
+ tabIds.indices.contains(item.tag) && tabIds[item.tag] == selectedId
1170
+ })
1131
1171
  }
1132
1172
 
1133
1173
  return (items, selectedIndex)
1134
1174
  }
1175
+
1135
1176
  private func makeFloatingTabBarItems(
1136
1177
  _ tabs: [[String: Any]],
1137
1178
  selectedId: String?,
@@ -1151,6 +1192,8 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1151
1192
  let title = tab["title"] as? String ?? ""
1152
1193
  let image = icons ? self.image(from: tab["icon"] as? [String: Any]) : nil
1153
1194
  let selectedImage = icons ? self.image(from: tab["selectedIcon"] as? [String: Any]) : nil
1195
+ let role = (tab["role"] as? String)?.lowercased()
1196
+ let isDetachedTrailing = role == "search" || role == "prominent"
1154
1197
  if id == selectedId {
1155
1198
  selectedIndex = visibleIndex
1156
1199
  }
@@ -1162,10 +1205,24 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1162
1205
  selectedImage: selectedImage,
1163
1206
  badge: tab["badge"].map { String(describing: $0) },
1164
1207
  enabled: tab["enabled"] as? Bool ?? true,
1208
+ isDetachedTrailing: isDetachedTrailing,
1165
1209
  sourceIndex: sourceIndex
1166
1210
  ))
1167
1211
  }
1168
1212
 
1213
+ // Keep at most one detached trailing action and place it last so the
1214
+ // capsule + circular button layout matches the system Liquid Glass pattern.
1215
+ // Skip reordering for curve bars so center-item selection stays stable.
1216
+ if tabbarStyle.shape == .floating,
1217
+ let trailingIndex = items.lastIndex(where: { $0.isDetachedTrailing }) {
1218
+ let trailing = items.remove(at: trailingIndex)
1219
+ items.removeAll(where: { $0.isDetachedTrailing })
1220
+ items.append(trailing)
1221
+ if let selectedId = selectedId {
1222
+ selectedIndex = items.firstIndex(where: { $0.id == selectedId })
1223
+ }
1224
+ }
1225
+
1169
1226
  return (items, selectedIndex)
1170
1227
  }
1171
1228
  private func tabTitle(
@@ -1518,19 +1575,37 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1518
1575
  let colors = call.getObject("colors")
1519
1576
  let backgroundTint = colorValue(colors?["background"])
1520
1577
  let backgroundColor = (backgroundTint ?? .systemBackground).withAlphaComponent(tabbarStyle.shape == .curve ? 0.96 : 0.46)
1578
+ let usesLiquidGlass = liquidGlassEffect() != nil
1521
1579
 
1522
1580
  if tabbarStyle.shape == .curve || prefersOpaqueTabBarBackground() {
1523
1581
  tabEffectView?.isHidden = true
1582
+ trailingTabEffectView?.isHidden = true
1583
+ } else if let glass = liquidGlassEffect() {
1584
+ tabEffectView?.effect = glass
1585
+ tabEffectView?.isHidden = false
1586
+ tabEffectView?.contentView.backgroundColor = backgroundTint?.withAlphaComponent(0.08) ?? .clear
1587
+ trailingTabEffectView?.effect = liquidGlassEffect() ?? glass
1588
+ trailingTabEffectView?.contentView.backgroundColor = backgroundTint?.withAlphaComponent(0.08) ?? .clear
1524
1589
  } else if let effect = blurEffect(from: call.getString("blurEffect"), fallback: .systemChromeMaterial) {
1525
1590
  tabEffectView?.effect = effect
1526
1591
  tabEffectView?.isHidden = false
1527
1592
  tabEffectView?.contentView.backgroundColor = backgroundTint?.withAlphaComponent(0.12) ?? backgroundColor
1593
+ trailingTabEffectView?.effect = effect
1594
+ trailingTabEffectView?.contentView.backgroundColor = backgroundTint?.withAlphaComponent(0.12) ?? backgroundColor
1528
1595
  } else {
1529
1596
  tabEffectView?.isHidden = true
1597
+ trailingTabEffectView?.isHidden = true
1530
1598
  }
1531
1599
 
1532
1600
  let opaqueBackground = backgroundTint ?? .systemBackground
1533
- tabBar.backgroundFillColor = prefersOpaqueTabBarBackground() ? opaqueBackground : backgroundColor
1601
+ if prefersOpaqueTabBarBackground() {
1602
+ tabBar.backgroundFillColor = opaqueBackground
1603
+ } else if usesLiquidGlass {
1604
+ // Keep the hand-drawn fill clear so UIGlassEffect can show through.
1605
+ tabBar.backgroundFillColor = .clear
1606
+ } else {
1607
+ tabBar.backgroundFillColor = backgroundColor
1608
+ }
1534
1609
  if let color = colorValue(colors?["tint"]) {
1535
1610
  tabBar.selectedTintColor = color
1536
1611
  }
@@ -1723,16 +1798,34 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1723
1798
  if let container = tabContainer {
1724
1799
  let availableWidth = max(0, width - (tabbarStyle.horizontalMargin * 2))
1725
1800
  let maxWidth = tabbarStyle.maxWidth > 0 ? tabbarStyle.maxWidth : availableWidth
1726
- let tabbarWidth = min(availableWidth, maxWidth)
1801
+ let hasDetachedTrailing = floatingTabBar?.hasDetachedTrailing == true && tabbarStyle.shape == .floating
1802
+ let trailingDiameter = tabbarStyle.height
1803
+ let trailingGap: CGFloat = 10
1804
+ let trailingExtra = hasDetachedTrailing ? trailingDiameter + trailingGap : 0
1805
+ let tabbarWidth = min(availableWidth, maxWidth + trailingExtra)
1727
1806
  let originX = (width - tabbarWidth) / 2
1728
1807
  let originY = height - safeInsets.bottom - tabbarStyle.bottomGap - tabbarStyle.totalHeight
1729
1808
  container.frame = CGRect(x: originX, y: originY, width: tabbarWidth, height: tabbarStyle.totalHeight)
1730
- container.layer.cornerRadius = tabbarStyle.shape == .floating ? tabbarStyle.cornerRadius : 0
1731
- container.layer.shadowPath = NativeNavigationTabbarBackgroundPath.path(in: container.bounds, style: tabbarStyle).cgPath
1732
- tabEffectView?.frame = container.bounds
1733
- tabEffectView?.layer.cornerRadius = tabbarStyle.cornerRadius
1734
1809
  floatingTabBar?.frame = container.bounds
1735
1810
  floatingTabBar?.layer.cornerRadius = 0
1811
+ floatingTabBar?.layoutIfNeeded()
1812
+
1813
+ let capsuleBounds = floatingTabBar?.capsuleBounds(in: container.bounds) ?? container.bounds
1814
+ container.layer.cornerRadius = tabbarStyle.shape == .floating ? tabbarStyle.cornerRadius : 0
1815
+ container.layer.shadowPath = NativeNavigationTabbarBackgroundPath.path(in: capsuleBounds, style: tabbarStyle).cgPath
1816
+ tabEffectView?.frame = capsuleBounds
1817
+ tabEffectView?.layer.cornerRadius = tabbarStyle.shape == .floating ? tabbarStyle.cornerRadius : 0
1818
+ tabEffectView?.isHidden = tabbarStyle.shape == .curve || prefersOpaqueTabBarBackground() || tabEffectView?.effect == nil
1819
+
1820
+ if let trailingBounds = floatingTabBar?.trailingActionBounds(in: container.bounds),
1821
+ hasDetachedTrailing,
1822
+ !prefersOpaqueTabBarBackground() {
1823
+ trailingTabEffectView?.frame = trailingBounds
1824
+ trailingTabEffectView?.layer.cornerRadius = trailingBounds.height / 2
1825
+ trailingTabEffectView?.isHidden = trailingTabEffectView?.effect == nil
1826
+ } else {
1827
+ trailingTabEffectView?.isHidden = true
1828
+ }
1736
1829
  }
1737
1830
 
1738
1831
  if let tabBarController = tabBarController {
@@ -1822,6 +1915,28 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1822
1915
  ][value]
1823
1916
  }
1824
1917
 
1918
+ private func liquidGlassEffect() -> UIVisualEffect? {
1919
+ guard usesSystemLiquidGlass,
1920
+ let effectClass = NSClassFromString("UIGlassEffect") else {
1921
+ return nil
1922
+ }
1923
+
1924
+ let styleSelector = NSSelectorFromString("effectWithStyle:")
1925
+ if let method = class_getClassMethod(effectClass, styleSelector) {
1926
+ typealias EffectWithStyle = @convention(c) (AnyClass, Selector, Int) -> AnyObject?
1927
+ let factory = unsafeBitCast(method_getImplementation(method), to: EffectWithStyle.self)
1928
+ if let effect = factory(effectClass, styleSelector, 0) as? UIVisualEffect {
1929
+ return effect
1930
+ }
1931
+ }
1932
+
1933
+ if let objectType = effectClass as? NSObject.Type {
1934
+ return objectType.init() as? UIVisualEffect
1935
+ }
1936
+
1937
+ return nil
1938
+ }
1939
+
1825
1940
  private func configureGlassBarButtonItem(_ item: UIBarButtonItem, id: String) {
1826
1941
  guard #available(iOS 26.0, *) else {
1827
1942
  return
@@ -1954,6 +2069,7 @@ private struct NativeNavigationFloatingTabItem {
1954
2069
  let selectedImage: UIImage?
1955
2070
  let badge: String?
1956
2071
  let enabled: Bool
2072
+ let isDetachedTrailing: Bool
1957
2073
  let sourceIndex: Int
1958
2074
  }
1959
2075
 
@@ -2029,6 +2145,7 @@ private struct NativeNavigationFloatingTabStyle {
2029
2145
  let labels: Bool
2030
2146
  let icons: Bool
2031
2147
  let isCenter: Bool
2148
+ let isDetachedTrailing: Bool
2032
2149
  let selectedTint: UIColor
2033
2150
  let inactiveTint: UIColor
2034
2151
  let centerButtonColor: UIColor
@@ -2065,6 +2182,10 @@ private final class NativeNavigationFloatingTabBar: UIView {
2065
2182
  }
2066
2183
  var onSelect: ((Int, NativeNavigationFloatingTabItem) -> Void)?
2067
2184
 
2185
+ var hasDetachedTrailing: Bool {
2186
+ tabbarStyle.shape == .floating && items.contains(where: \.isDetachedTrailing)
2187
+ }
2188
+
2068
2189
  override init(frame: CGRect) {
2069
2190
  super.init(frame: frame)
2070
2191
  isOpaque = false
@@ -2094,9 +2215,33 @@ private final class NativeNavigationFloatingTabBar: UIView {
2094
2215
  setNeedsLayout()
2095
2216
  }
2096
2217
 
2218
+ func capsuleBounds(in bounds: CGRect) -> CGRect {
2219
+ guard hasDetachedTrailing else {
2220
+ return bounds
2221
+ }
2222
+ let trailingGap: CGFloat = 10
2223
+ let trailingDiameter = tabbarStyle.height
2224
+ let capsuleWidth = max(0, bounds.width - trailingDiameter - trailingGap)
2225
+ return CGRect(x: bounds.minX, y: bounds.minY, width: capsuleWidth, height: bounds.height)
2226
+ }
2227
+
2228
+ func trailingActionBounds(in bounds: CGRect) -> CGRect? {
2229
+ guard hasDetachedTrailing else {
2230
+ return nil
2231
+ }
2232
+ let trailingDiameter = tabbarStyle.height
2233
+ return CGRect(
2234
+ x: bounds.maxX - trailingDiameter,
2235
+ y: bounds.minY + (bounds.height - trailingDiameter) / 2,
2236
+ width: trailingDiameter,
2237
+ height: trailingDiameter
2238
+ )
2239
+ }
2240
+
2097
2241
  override func layoutSubviews() {
2098
2242
  super.layoutSubviews()
2099
- backgroundShapeView.frame = bounds
2243
+ let capsule = capsuleBounds(in: bounds)
2244
+ backgroundShapeView.frame = capsule
2100
2245
  guard !buttons.isEmpty else {
2101
2246
  return
2102
2247
  }
@@ -2123,6 +2268,15 @@ private final class NativeNavigationFloatingTabBar: UIView {
2123
2268
  return
2124
2269
  }
2125
2270
 
2271
+ if let trailingIndex = detachedTrailingIndex(),
2272
+ buttons.indices.contains(trailingIndex),
2273
+ let trailingBounds = trailingActionBounds(in: bounds) {
2274
+ buttons[trailingIndex].frame = trailingBounds
2275
+ let mainIndices = buttons.indices.filter { $0 != trailingIndex }
2276
+ layoutButtons(mainIndices, in: capsule)
2277
+ return
2278
+ }
2279
+
2126
2280
  layoutButtons(Array(buttons.indices), in: bounds)
2127
2281
  }
2128
2282
 
@@ -2152,6 +2306,13 @@ private final class NativeNavigationFloatingTabBar: UIView {
2152
2306
  return items.count / 2
2153
2307
  }
2154
2308
 
2309
+ private func detachedTrailingIndex() -> Int? {
2310
+ guard tabbarStyle.shape == .floating else {
2311
+ return nil
2312
+ }
2313
+ return items.lastIndex(where: \.isDetachedTrailing)
2314
+ }
2315
+
2155
2316
  private func rebuildButtons() {
2156
2317
  buttons.forEach { $0.removeFromSuperview() }
2157
2318
  buttons = items.enumerated().map { index, item in
@@ -2181,11 +2342,16 @@ private final class NativeNavigationFloatingTabBar: UIView {
2181
2342
 
2182
2343
  private func style(for index: Int) -> NativeNavigationFloatingTabStyle {
2183
2344
  let isCenter = centerButtonIndex() == index
2345
+ let isDetachedTrailing = detachedTrailingIndex() == index
2346
+ // Detached actions stay icon-only when icons are available; fall back to
2347
+ // a label when icons are disabled so the control remains discoverable.
2348
+ let detachedShowsLabel = isDetachedTrailing && !iconsVisible && showsLabel(for: index)
2184
2349
  return NativeNavigationFloatingTabStyle(
2185
2350
  selected: index == selectedIndex,
2186
- labels: showsLabel(for: index),
2351
+ labels: isDetachedTrailing ? detachedShowsLabel : showsLabel(for: index),
2187
2352
  icons: iconsVisible,
2188
2353
  isCenter: isCenter,
2354
+ isDetachedTrailing: isDetachedTrailing,
2189
2355
  selectedTint: selectedTintColor,
2190
2356
  inactiveTint: inactiveTintColor,
2191
2357
  centerButtonColor: tabbarStyle.centerButtonColor ?? selectedTintColor,
@@ -2293,13 +2459,20 @@ private final class NativeNavigationFloatingTabButton: UIControl {
2293
2459
  isEnabled = item.enabled
2294
2460
  alpha = item.enabled ? 1 : 0.38
2295
2461
  isCenterButton = style.isCenter
2462
+ let isDetachedTrailing = style.isDetachedTrailing
2296
2463
  hasIcon = style.icons && (item.image != nil || item.selectedImage != nil)
2297
- hasLabel = style.isCenter ? (!hasIcon && !item.title.isEmpty) : (style.labels && !item.title.isEmpty)
2464
+ hasLabel = isDetachedTrailing
2465
+ ? (!hasIcon && style.labels && !item.title.isEmpty)
2466
+ : (style.isCenter ? (!hasIcon && !item.title.isEmpty) : (style.labels && !item.title.isEmpty))
2298
2467
  badgeText = item.badge
2299
2468
 
2300
- let color = style.isCenter ? style.centerButtonIconColor : (style.selected ? style.selectedTint : style.inactiveTint)
2301
- selectedView.backgroundColor = style.isCenter ? style.centerButtonColor : style.selectedTint.withAlphaComponent(style.selected ? 0.16 : 0)
2302
- selectedView.alpha = style.isCenter || style.selected ? 1 : 0
2469
+ let color = style.isCenter
2470
+ ? style.centerButtonIconColor
2471
+ : (style.selected ? style.selectedTint : style.inactiveTint)
2472
+ selectedView.backgroundColor = style.isCenter
2473
+ ? style.centerButtonColor
2474
+ : style.selectedTint.withAlphaComponent(style.selected && !isDetachedTrailing ? 0.16 : 0)
2475
+ selectedView.alpha = style.isCenter || (style.selected && !isDetachedTrailing) ? 1 : 0
2303
2476
 
2304
2477
  layer.shadowColor = UIColor.black.cgColor
2305
2478
  layer.shadowOpacity = style.isCenter ? 0.2 : 0
@@ -2336,7 +2509,7 @@ private final class NativeNavigationFloatingTabButton: UIControl {
2336
2509
  selectedView.layer.cornerRadius = selectedView.bounds.height / 2
2337
2510
 
2338
2511
  let iconSize: CGFloat = isCenterButton ? 32 : 23
2339
- if isCenterButton {
2512
+ if isCenterButton || (!hasLabel && hasIcon && bounds.width <= bounds.height + 1) {
2340
2513
  if hasIcon {
2341
2514
  imageView.frame = CGRect(x: (bounds.width - iconSize) / 2, y: (bounds.height - iconSize) / 2, width: iconSize, height: iconSize)
2342
2515
  titleLabel.frame = .zero
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.2.9",
3
+ "version": "8.3.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",
@@ -57,7 +57,8 @@
57
57
  "example:sync": "bun run example:build && cd example-app && bunx cap sync",
58
58
  "example:ios": "bun run example:build && cd example-app && ([ -d ios ] || bunx cap add ios) && bunx cap sync ios && bunx cap run ios",
59
59
  "example:android": "bun run example:build && cd example-app && ([ -d android ] || bunx cap add android) && bunx cap sync android && bunx cap run android",
60
- "example:install": "cd example-app && bun install --frozen-lockfile"
60
+ "example:install": "cd example-app && bun install --frozen-lockfile",
61
+ "changelog:ai": "node scripts/generate-ai-changelog.mjs"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@capacitor/android": "^8.0.0",