@capgo/capacitor-native-navigation 8.2.10 → 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 +36 -22
- package/android/src/main/java/app/capgo/nativenavigation/NativeNavigationPlugin.java +108 -10
- package/dist/docs.json +29 -1
- package/dist/esm/definitions.d.ts +25 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/NativeNavigationPlugin/NativeNavigationPlugin.swift +197 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -791,34 +791,35 @@ Native tabbar state.
|
|
|
791
791
|
|
|
792
792
|
A native tab item.
|
|
793
793
|
|
|
794
|
-
| Prop | Type
|
|
795
|
-
| ------------------ |
|
|
796
|
-
| **`id`** | <code>string</code>
|
|
797
|
-
| **`title`** | <code>string</code>
|
|
798
|
-
| **`icon`** | <code><a href="#nativenavigationicon">NativeNavigationIcon</a></code>
|
|
799
|
-
| **`selectedIcon`** | <code><a href="#nativenavigationicon">NativeNavigationIcon</a></code>
|
|
800
|
-
| **`badge`** | <code>string \| number</code>
|
|
801
|
-
| **`enabled`** | <code>boolean</code>
|
|
802
|
-
| **`hidden`** | <code>boolean</code>
|
|
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
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
327
|
-
*
|
|
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:
|
|
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
|
|
1113
|
-
item
|
|
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
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
2301
|
-
|
|
2302
|
-
|
|
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.
|
|
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",
|