@capgo/capacitor-inappbrowser 8.16.5 → 8.17.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
@@ -1606,6 +1606,7 @@ See the README section "openSecureWindow (OAuth)" for full setup examples.
1606
1606
  | **`isAnimated`** | <code>boolean</code> | Whether the webview opening is animated or not, ios only | <code>true</code> | |
1607
1607
  | **`showReloadButton`** | <code>boolean</code> | Shows a reload button that reloads the web page | <code>false</code> | 1.0.15 |
1608
1608
  | **`closeAction`** | <code><a href="#closeaction">CloseAction</a></code> | closeAction controls what happens when the native toolbar close button is pressed. This does not change the behavior of close(), JavaScript window.mobileApp.close(), or native back navigation. | <code>CloseAction.CLOSE</code> | 8.7.7 |
1609
+ | **`closeButtonPosition`** | <code><a href="#closebuttonposition">CloseButtonPosition</a></code> | Where the native toolbar close button is placed. When omitted, each platform keeps its convention: Android places the close button at the start, iOS at the end (or at the start when `showArrow` is true). Setting this option overrides that placement on both platforms. | | 8.17.0 |
1609
1610
  | **`screenshotOnHide`** | <code>boolean</code> | Captures the visible webview and includes it as `screenshot` in `hideEvent` before the toolbar close button hides the webview. Only applies when `closeAction` is <a href="#closeaction">`CloseAction.HIDE`</a>. On iOS, capture uses `WKWebView.takeSnapshot`. When testing through Apple's iPhone Mirroring app with the physical device locked, the snapshot can succeed with the correct dimensions but a fully transparent PNG. Capture works as expected on a normal unlocked device. This appears to be an Apple mirroring limitation rather than a plugin bug. | <code>false</code> | 8.7.10 |
1610
1611
  | **`closeModal`** | <code>boolean</code> | CloseModal: if true a confirm will be displayed when user clicks on close button, if false the browser will be closed immediately. | <code>false</code> | 1.1.0 |
1611
1612
  | **`closeModalTitle`** | <code>string</code> | CloseModalTitle: title of the confirm when user clicks on close button | <code>"Close"</code> | 1.1.0 |
@@ -2015,6 +2016,14 @@ Native handling mode used after a managed download finishes.
2015
2016
  | **`HIDE`** | <code>'hide'</code> | The toolbar close button hides the webview so it can be shown again. |
2016
2017
 
2017
2018
 
2019
+ #### CloseButtonPosition
2020
+
2021
+ | Members | Value | Description |
2022
+ | ----------- | -------------------- | -------------------------------------------------------------- |
2023
+ | **`START`** | <code>'start'</code> | Leading edge of the toolbar (left in left-to-right layouts). |
2024
+ | **`END`** | <code>'end'</code> | Trailing edge of the toolbar (right in left-to-right layouts). |
2025
+
2026
+
2018
2027
  #### InvisibilityMode
2019
2028
 
2020
2029
  | Members | Value | Description |
@@ -1072,6 +1072,15 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
1072
1072
  return;
1073
1073
  }
1074
1074
  options.setCloseAction(closeAction);
1075
+ String closeButtonPosition = call.getString("closeButtonPosition");
1076
+ if (closeButtonPosition != null) {
1077
+ closeButtonPosition = closeButtonPosition.toLowerCase(Locale.ROOT);
1078
+ if (CloseButtonPositionSupport.gravityFor(closeButtonPosition) == null) {
1079
+ call.reject("closeButtonPosition must be 'start' or 'end'");
1080
+ return;
1081
+ }
1082
+ }
1083
+ options.setCloseButtonPosition(closeButtonPosition);
1075
1084
  options.setTitleFontFamily(call.getString("titleFontFamily"));
1076
1085
  JSObject titleIconObj = call.getObject("titleIcon");
1077
1086
  if (titleIconObj != null) {
@@ -0,0 +1,35 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import android.view.Gravity;
4
+
5
+ /**
6
+ * Maps the {@code closeButtonPosition} option onto toolbar layout values.
7
+ */
8
+ final class CloseButtonPositionSupport {
9
+
10
+ /** End padding that visually matches the start side's toolbar content inset. */
11
+ static final int END_PADDING_DP = 20;
12
+
13
+ private CloseButtonPositionSupport() {}
14
+
15
+ /** Toolbar gravity for the option value, or {@code null} to keep the layout default. */
16
+ static Integer gravityFor(String closeButtonPosition) {
17
+ if ("start".equals(closeButtonPosition)) {
18
+ return Gravity.START;
19
+ }
20
+ if ("end".equals(closeButtonPosition)) {
21
+ return Gravity.END;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ /**
27
+ * Keeps the layout's edge nudge pointing outwards for the resolved gravity.
28
+ * Resolves start/end against the layout direction so RTL mirrors correctly.
29
+ */
30
+ static float mirroredTranslationX(int gravity, int layoutDirection, float translationX) {
31
+ int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
32
+ float outwards = Math.abs(translationX);
33
+ return absoluteGravity == Gravity.RIGHT ? outwards : -outwards;
34
+ }
35
+ }
@@ -146,6 +146,7 @@ public class Options {
146
146
  private String CloseModalCancel;
147
147
  private ButtonNearDone buttonNearDone;
148
148
  private String closeAction = "close";
149
+ private String closeButtonPosition;
149
150
  private String titleFontFamily;
150
151
  private ButtonNearDone titleIcon;
151
152
  private String CloseModalOk;
@@ -466,6 +467,14 @@ public class Options {
466
467
  this.closeAction = closeAction;
467
468
  }
468
469
 
470
+ public String getCloseButtonPosition() {
471
+ return closeButtonPosition;
472
+ }
473
+
474
+ public void setCloseButtonPosition(String closeButtonPosition) {
475
+ this.closeButtonPosition = closeButtonPosition;
476
+ }
477
+
469
478
  public String getTitleFontFamily() {
470
479
  return titleFontFamily;
471
480
  }
@@ -834,6 +843,7 @@ public class Options {
834
843
  copy.setCloseModalURLPattern(closeModalURLPattern);
835
844
  copy.setButtonNearDone(buttonNearDone);
836
845
  copy.setCloseAction(closeAction);
846
+ copy.setCloseButtonPosition(closeButtonPosition);
837
847
  copy.setTitleFontFamily(titleFontFamily);
838
848
  copy.setTitleIcon(titleIcon);
839
849
  copy.setUrl("about:blank");
@@ -16,11 +16,13 @@ final class OrientationLayoutSupport {
16
16
  Integer previousScreenHeightDp,
17
17
  Integer previousSmallestScreenWidthDp,
18
18
  Integer previousDensityDpi,
19
+ Integer previousUiMode,
19
20
  int currentOrientation,
20
21
  int currentScreenWidthDp,
21
22
  int currentScreenHeightDp,
22
23
  int currentSmallestScreenWidthDp,
23
- int currentDensityDpi
24
+ int currentDensityDpi,
25
+ int currentUiMode
24
26
  ) {
25
27
  if (previousOrientation == null) {
26
28
  return true;
@@ -35,7 +37,9 @@ final class OrientationLayoutSupport {
35
37
  previousSmallestScreenWidthDp == null ||
36
38
  previousSmallestScreenWidthDp != currentSmallestScreenWidthDp ||
37
39
  previousDensityDpi == null ||
38
- previousDensityDpi != currentDensityDpi
40
+ previousDensityDpi != currentDensityDpi ||
41
+ previousUiMode == null ||
42
+ previousUiMode != currentUiMode
39
43
  );
40
44
  }
41
45
  }
@@ -34,6 +34,17 @@ final class SystemUiChromeSupport {
34
34
  return !edgeToEdge && shouldUsePreApi30LayoutFlags(sdkInt);
35
35
  }
36
36
 
37
+ /**
38
+ * Color painted behind the browser content, visible in the system bar insets. Follows the toolbar
39
+ * color when one is set, otherwise the system theme, so the dialog theme's default never shows.
40
+ */
41
+ static int resolveWindowBackgroundColor(Integer toolbarColor, boolean darkTheme) {
42
+ if (toolbarColor != null) {
43
+ return toolbarColor;
44
+ }
45
+ return darkTheme ? Color.BLACK : Color.WHITE;
46
+ }
47
+
37
48
  static void setDecorFitsSystemWindows(Window window, boolean decorFitsSystemWindows) {
38
49
  if (window == null) {
39
50
  return;
@@ -25,7 +25,9 @@ final class WebViewBackNavigationSupport {
25
25
  /**
26
26
  * When {@code disableGoBackOnNativeApplication} is true, the dialog must not be cancelable on
27
27
  * back/gesture. Otherwise {@link androidx.activity.ComponentDialog}'s built-in back callback
28
- * dismisses the dialog before our handler can return {@link Action#IGNORE}.
28
+ * dismisses the dialog before our handler can return {@link Action#IGNORE}. While cancelable is
29
+ * false that built-in callback still consumes back without dismissing, so {@link WebViewDialog}
30
+ * must keep its own callback registered last on the dispatcher.
29
31
  */
30
32
  static boolean isCancelableOnBack(boolean disableGoBackOnNativeApplication) {
31
33
  return !disableGoBackOnNativeApplication;
@@ -469,7 +469,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
469
469
  @Override
470
470
  protected void onStart() {
471
471
  super.onStart();
472
- syncBackNavigationHandlers();
472
+ // ComponentDialog registers its own back callback in onCreate; re-add ours last so
473
+ // handleBrowserBackNavigation runs before the built-in cancel callback (LIFO order).
474
+ ensureDialogBackHandlerOnTop();
473
475
  }
474
476
 
475
477
  @Override
@@ -2212,9 +2214,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
2212
2214
  boolean isDarkBackground = isDarkColor(toolbarColor);
2213
2215
  insetsController.setAppearanceLightStatusBars(!isDarkBackground);
2214
2216
  } catch (IllegalArgumentException e) {
2215
- // Fallback to default black if color parsing fails
2216
- statusBarColorView.setBackgroundColor(Color.BLACK);
2217
- insetsController.setAppearanceLightStatusBars(false);
2217
+ int fallbackColor = resolveWindowBackgroundColor();
2218
+ statusBarColorView.setBackgroundColor(fallbackColor);
2219
+ insetsController.setAppearanceLightStatusBars(!isDarkThemeEnabled());
2218
2220
  }
2219
2221
  } else {
2220
2222
  // Follow system dark mode if no toolbar color provided
@@ -2921,6 +2923,19 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
2921
2923
  if (dialogBackCallback != null) {
2922
2924
  return;
2923
2925
  }
2926
+ ensureDialogBackHandlerOnTop();
2927
+ }
2928
+
2929
+ /**
2930
+ * Re-register the dialog back callback so it sits above {@link ComponentDialog}'s built-in
2931
+ * callback. That callback is always enabled and consumes back without dismissing when
2932
+ * {@link #setCancelable(boolean)} is false, which would block webview history navigation.
2933
+ */
2934
+ private void ensureDialogBackHandlerOnTop() {
2935
+ if (dialogBackCallback != null) {
2936
+ dialogBackCallback.remove();
2937
+ dialogBackCallback = null;
2938
+ }
2924
2939
  // Predictive back (API 33+) never delivers KEYCODE_BACK to Dialog OnKeyListener.
2925
2940
  dialogBackCallback = new OnBackPressedCallback(false) {
2926
2941
  @Override
@@ -2949,7 +2964,11 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
2949
2964
  void applyBackNavigationPolicy() {
2950
2965
  boolean disableGoBack = _options != null && _options.getDisableGoBackOnNativeApplication();
2951
2966
  setCancelable(WebViewBackNavigationSupport.isCancelableOnBack(disableGoBack));
2952
- syncBackNavigationHandlers();
2967
+ if (isShowing()) {
2968
+ ensureDialogBackHandlerOnTop();
2969
+ } else {
2970
+ syncBackNavigationHandlers();
2971
+ }
2953
2972
  }
2954
2973
 
2955
2974
  private void syncBackNavigationHandlers() {
@@ -3431,6 +3450,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3431
3450
  Integer previousScreenHeightDp = lastConfiguration != null ? lastConfiguration.screenHeightDp : null;
3432
3451
  Integer previousSmallestScreenWidthDp = lastConfiguration != null ? lastConfiguration.smallestScreenWidthDp : null;
3433
3452
  Integer previousDensityDpi = lastConfiguration != null ? lastConfiguration.densityDpi : null;
3453
+ Integer previousUiMode = lastConfiguration != null ? lastConfiguration.uiMode : null;
3434
3454
 
3435
3455
  int currentOrientation = newConfig != null ? newConfig.orientation : (previousOrientation != null ? previousOrientation : 0);
3436
3456
  int currentScreenWidthDp =
@@ -3442,6 +3462,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3442
3462
  ? newConfig.smallestScreenWidthDp
3443
3463
  : (previousSmallestScreenWidthDp != null ? previousSmallestScreenWidthDp : 0);
3444
3464
  int currentDensityDpi = newConfig != null ? newConfig.densityDpi : (previousDensityDpi != null ? previousDensityDpi : 0);
3465
+ int currentUiMode = newConfig != null ? newConfig.uiMode : (previousUiMode != null ? previousUiMode : 0);
3445
3466
 
3446
3467
  boolean shouldRefresh = OrientationLayoutSupport.shouldRefreshBrowserLayout(
3447
3468
  previousOrientation,
@@ -3449,11 +3470,13 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3449
3470
  previousScreenHeightDp,
3450
3471
  previousSmallestScreenWidthDp,
3451
3472
  previousDensityDpi,
3473
+ previousUiMode,
3452
3474
  currentOrientation,
3453
3475
  currentScreenWidthDp,
3454
3476
  currentScreenHeightDp,
3455
3477
  currentSmallestScreenWidthDp,
3456
- currentDensityDpi
3478
+ currentDensityDpi,
3479
+ currentUiMode
3457
3480
  );
3458
3481
 
3459
3482
  if (newConfig != null) {
@@ -3494,6 +3517,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3494
3517
  }
3495
3518
 
3496
3519
  ensureContentBrowserMatchParentHeight();
3520
+ applyWindowBackgroundColor();
3497
3521
 
3498
3522
  boolean isBlankToolbar = _options != null && TextUtils.equals(_options.getToolbarType(), "blank");
3499
3523
  if (isBlankToolbar) {
@@ -3580,18 +3604,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3580
3604
  appBarLayout.setStateListAnimator(null);
3581
3605
  appBarLayout.setOutlineProvider(null);
3582
3606
 
3583
- int backgroundColor = Color.BLACK;
3584
- if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3585
- try {
3586
- backgroundColor = Color.parseColor(_options.getToolbarColor());
3587
- } catch (IllegalArgumentException e) {
3588
- Log.e("InAppBrowser", "Invalid toolbar color, using black: " + e.getMessage());
3589
- }
3590
- } else {
3591
- backgroundColor = isDarkThemeEnabled() ? Color.BLACK : Color.WHITE;
3592
- }
3593
-
3594
- final int finalBgColor = backgroundColor;
3607
+ final int finalBgColor = resolveWindowBackgroundColor();
3595
3608
  _webView.post(() -> {
3596
3609
  if (_webView == null) {
3597
3610
  return;
@@ -3611,6 +3624,12 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3611
3624
 
3612
3625
  applyAppBarTopInset(appBarLayout, appBarHandlesTopInset(toolbarView) ? statusBarHeight : 0);
3613
3626
  appBarLayout.setBackgroundColor(finalBgColor);
3627
+
3628
+ Window window = getWindow();
3629
+ if (window != null) {
3630
+ WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(window, window.getDecorView());
3631
+ insetsController.setAppearanceLightStatusBars(!isDarkColor(finalBgColor));
3632
+ }
3614
3633
  });
3615
3634
  }
3616
3635
 
@@ -3855,6 +3874,29 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3855
3874
  }
3856
3875
  }
3857
3876
 
3877
+ private int resolveWindowBackgroundColor() {
3878
+ Integer toolbarColor = null;
3879
+ if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3880
+ try {
3881
+ toolbarColor = Color.parseColor(_options.getToolbarColor());
3882
+ } catch (IllegalArgumentException e) {
3883
+ Log.e("InAppBrowser", "Invalid toolbar color, using theme default: " + e.getMessage());
3884
+ }
3885
+ }
3886
+ return SystemUiChromeSupport.resolveWindowBackgroundColor(toolbarColor, isDarkThemeEnabled());
3887
+ }
3888
+
3889
+ private void applyWindowBackgroundColor() {
3890
+ // Custom dimensions keep the window transparent for touch passthrough
3891
+ if (_options.getWidth() != null || _options.getHeight() != null) {
3892
+ return;
3893
+ }
3894
+ Window window = getWindow();
3895
+ if (window != null) {
3896
+ window.getDecorView().setBackgroundColor(resolveWindowBackgroundColor());
3897
+ }
3898
+ }
3899
+
3858
3900
  private int getSystemStatusBarHeight() {
3859
3901
  int resourceId = getContext().getResources().getIdentifier("status_bar_height", "dimen", "android");
3860
3902
  if (resourceId <= 0) {
@@ -4932,62 +4974,85 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
4932
4974
 
4933
4975
  private void setupToolbar() {
4934
4976
  _toolbar = findViewById(R.id.tool_bar);
4977
+ applyWindowBackgroundColor();
4935
4978
 
4936
4979
  // Apply toolbar color early, for ALL toolbar types, before any view configuration
4937
4980
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
4981
+ int toolbarColor;
4938
4982
  try {
4939
- int toolbarColor = Color.parseColor(_options.getToolbarColor());
4940
- _toolbar.setBackgroundColor(toolbarColor);
4983
+ toolbarColor = Color.parseColor(_options.getToolbarColor());
4984
+ } catch (IllegalArgumentException e) {
4985
+ Log.e("InAppBrowser", "Invalid toolbar color, using theme default: " + e.getMessage());
4986
+ toolbarColor = resolveWindowBackgroundColor();
4987
+ }
4941
4988
 
4942
- // Get toolbar title and ensure it gets the right color
4943
- TextView titleText = _toolbar.findViewById(R.id.titleText);
4989
+ _toolbar.setBackgroundColor(toolbarColor);
4944
4990
 
4945
- // Determine icon and text color
4946
- cachedTitleIconDrawable = null;
4947
- cachedTitleIconResolved = false;
4991
+ // Get toolbar title and ensure it gets the right color
4992
+ TextView titleText = _toolbar.findViewById(R.id.titleText);
4948
4993
 
4949
- int iconColor;
4950
- if (_options.getToolbarTextColor() != null && !_options.getToolbarTextColor().isEmpty()) {
4951
- try {
4952
- iconColor = Color.parseColor(_options.getToolbarTextColor());
4953
- } catch (IllegalArgumentException e) {
4954
- // Fallback to automatic detection if parsing fails
4955
- boolean isDarkBackground = isDarkColor(toolbarColor);
4956
- iconColor = isDarkBackground ? Color.WHITE : Color.BLACK;
4957
- }
4958
- } else {
4959
- // No explicit toolbarTextColor, use automatic detection based on background
4994
+ // Determine icon and text color
4995
+ cachedTitleIconDrawable = null;
4996
+ cachedTitleIconResolved = false;
4997
+
4998
+ int iconColor;
4999
+ if (_options.getToolbarTextColor() != null && !_options.getToolbarTextColor().isEmpty()) {
5000
+ try {
5001
+ iconColor = Color.parseColor(_options.getToolbarTextColor());
5002
+ } catch (IllegalArgumentException e) {
5003
+ // Fallback to automatic detection if parsing fails
4960
5004
  boolean isDarkBackground = isDarkColor(toolbarColor);
4961
5005
  iconColor = isDarkBackground ? Color.WHITE : Color.BLACK;
4962
5006
  }
5007
+ } else {
5008
+ // No explicit toolbarTextColor, use automatic detection based on background
5009
+ boolean isDarkBackground = isDarkColor(toolbarColor);
5010
+ iconColor = isDarkBackground ? Color.WHITE : Color.BLACK;
5011
+ }
4963
5012
 
4964
- // Store for later use with navigation buttons
4965
- this.iconColor = iconColor;
5013
+ // Store for later use with navigation buttons
5014
+ this.iconColor = iconColor;
4966
5015
 
4967
- // Set title text color directly
4968
- titleText.setTextColor(iconColor);
5016
+ // Set title text color directly
5017
+ titleText.setTextColor(iconColor);
4969
5018
 
4970
- // Apply colors to all buttons
4971
- applyColorToAllButtons(toolbarColor, iconColor);
5019
+ // Apply colors to all buttons
5020
+ applyColorToAllButtons(toolbarColor, iconColor);
4972
5021
 
4973
- // Also ensure status bar gets the color
4974
- if (getWindow() != null) {
4975
- SystemUiChromeSupport.applyLegacyStatusBarColorViaView(findViewById(R.id.status_bar_color_view), toolbarColor);
5022
+ // Also ensure status bar gets the color
5023
+ if (getWindow() != null) {
5024
+ SystemUiChromeSupport.applyLegacyStatusBarColorViaView(findViewById(R.id.status_bar_color_view), toolbarColor);
4976
5025
 
4977
- // Determine proper status bar text color (light or dark icons)
4978
- boolean isDarkBackground = isDarkColor(toolbarColor);
4979
- WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(
4980
- getWindow(),
4981
- getWindow().getDecorView()
4982
- );
4983
- insetsController.setAppearanceLightStatusBars(!isDarkBackground);
4984
- }
4985
- } catch (IllegalArgumentException e) {
4986
- Log.e("InAppBrowser", "Invalid toolbar color: " + _options.getToolbarColor());
5026
+ // Determine proper status bar text color (light or dark icons)
5027
+ boolean isDarkBackground = isDarkColor(toolbarColor);
5028
+ WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
5029
+ insetsController.setAppearanceLightStatusBars(!isDarkBackground);
4987
5030
  }
4988
5031
  }
4989
5032
 
4990
5033
  ImageButton closeButtonView = _toolbar.findViewById(R.id.closeButton);
5034
+ Integer closeButtonGravity = CloseButtonPositionSupport.gravityFor(_options.getCloseButtonPosition());
5035
+ if (closeButtonGravity != null && closeButtonView.getLayoutParams() instanceof Toolbar.LayoutParams closeButtonParams) {
5036
+ int verticalGravity = closeButtonParams.gravity & Gravity.VERTICAL_GRAVITY_MASK;
5037
+ closeButtonParams.gravity = verticalGravity | closeButtonGravity;
5038
+ closeButtonView.setLayoutParams(closeButtonParams);
5039
+ closeButtonView.setTranslationX(
5040
+ CloseButtonPositionSupport.mirroredTranslationX(
5041
+ closeButtonGravity,
5042
+ _context.getResources().getConfiguration().getLayoutDirection(),
5043
+ closeButtonView.getTranslationX()
5044
+ )
5045
+ );
5046
+ // Toolbar only insets the start edge; pad the logical end so the icon keeps clear of the screen edge
5047
+ if (closeButtonGravity == Gravity.END) {
5048
+ closeButtonView.setPaddingRelative(
5049
+ closeButtonView.getPaddingStart(),
5050
+ closeButtonView.getPaddingTop(),
5051
+ (int) getPixels(CloseButtonPositionSupport.END_PADDING_DP),
5052
+ closeButtonView.getPaddingBottom()
5053
+ );
5054
+ }
5055
+ }
4991
5056
  closeButtonView.setOnClickListener(
4992
5057
  new View.OnClickListener() {
4993
5058
  @Override
@@ -5091,41 +5156,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
5091
5156
  configureBlankToolbarLayout();
5092
5157
  requestSafeAreaInsets();
5093
5158
 
5094
- // Also set window background color to match status bar for blank toolbar
5159
+ // Without a toolbar the status bar view is the only chrome, so it follows the window color
5095
5160
  View statusBarColorView = findViewById(R.id.status_bar_color_view);
5096
- if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
5097
- try {
5098
- int toolbarColor = Color.parseColor(_options.getToolbarColor());
5099
- if (getWindow() != null) {
5100
- getWindow().getDecorView().setBackgroundColor(toolbarColor);
5101
- }
5102
- // Also set status bar color view background if available
5103
- if (statusBarColorView != null) {
5104
- statusBarColorView.setBackgroundColor(toolbarColor);
5105
- }
5106
- } catch (IllegalArgumentException e) {
5107
- // Fallback to system default if color parsing fails
5108
- boolean isDarkTheme = isDarkThemeEnabled();
5109
- int windowBackgroundColor = isDarkTheme ? Color.BLACK : Color.WHITE;
5110
- if (getWindow() != null) {
5111
- getWindow().getDecorView().setBackgroundColor(windowBackgroundColor);
5112
- }
5113
- // Also set status bar color view background if available
5114
- if (statusBarColorView != null) {
5115
- statusBarColorView.setBackgroundColor(windowBackgroundColor);
5116
- }
5117
- }
5118
- } else {
5119
- // Follow system dark mode
5120
- boolean isDarkTheme = isDarkThemeEnabled();
5121
- int windowBackgroundColor = isDarkTheme ? Color.BLACK : Color.WHITE;
5122
- if (getWindow() != null) {
5123
- getWindow().getDecorView().setBackgroundColor(windowBackgroundColor);
5124
- }
5125
- // Also set status bar color view background if available
5126
- if (statusBarColorView != null) {
5127
- statusBarColorView.setBackgroundColor(windowBackgroundColor);
5128
- }
5161
+ if (statusBarColorView != null) {
5162
+ statusBarColorView.setBackgroundColor(resolveWindowBackgroundColor());
5129
5163
  }
5130
5164
  } else {
5131
5165
  _toolbar.findViewById(R.id.forwardButton).setVisibility(View.GONE);
package/dist/docs.json CHANGED
@@ -1954,6 +1954,24 @@
1954
1954
  ],
1955
1955
  "type": "CloseAction"
1956
1956
  },
1957
+ {
1958
+ "name": "closeButtonPosition",
1959
+ "tags": [
1960
+ {
1961
+ "text": "8.17.0",
1962
+ "name": "since"
1963
+ },
1964
+ {
1965
+ "text": "closeButtonPosition: CloseButtonPosition.END",
1966
+ "name": "example"
1967
+ }
1968
+ ],
1969
+ "docs": "Where the native toolbar close button is placed.\n\nWhen omitted, each platform keeps its convention: Android places the close button at the\nstart, iOS at the end (or at the start when `showArrow` is true). Setting this option\noverrides that placement on both platforms.",
1970
+ "complexTypes": [
1971
+ "CloseButtonPosition"
1972
+ ],
1973
+ "type": "CloseButtonPosition"
1974
+ },
1957
1975
  {
1958
1976
  "name": "screenshotOnHide",
1959
1977
  "tags": [
@@ -3654,6 +3672,24 @@
3654
3672
  }
3655
3673
  ]
3656
3674
  },
3675
+ {
3676
+ "name": "CloseButtonPosition",
3677
+ "slug": "closebuttonposition",
3678
+ "members": [
3679
+ {
3680
+ "name": "START",
3681
+ "value": "'start'",
3682
+ "tags": [],
3683
+ "docs": "Leading edge of the toolbar (left in left-to-right layouts)."
3684
+ },
3685
+ {
3686
+ "name": "END",
3687
+ "value": "'end'",
3688
+ "tags": [],
3689
+ "docs": "Trailing edge of the toolbar (right in left-to-right layouts)."
3690
+ }
3691
+ ]
3692
+ },
3657
3693
  {
3658
3694
  "name": "InvisibilityMode",
3659
3695
  "slug": "invisibilitymode",
@@ -105,6 +105,16 @@ export declare enum CloseAction {
105
105
  */
106
106
  HIDE = "hide"
107
107
  }
108
+ export declare enum CloseButtonPosition {
109
+ /**
110
+ * Leading edge of the toolbar (left in left-to-right layouts).
111
+ */
112
+ START = "start",
113
+ /**
114
+ * Trailing edge of the toolbar (right in left-to-right layouts).
115
+ */
116
+ END = "end"
117
+ }
108
118
  export interface ToolbarTitleIconOptions {
109
119
  ios?: {
110
120
  iconType: 'sf-symbol' | 'asset';
@@ -975,6 +985,18 @@ export interface OpenWebViewOptions {
975
985
  * closeAction: CloseAction.HIDE
976
986
  */
977
987
  closeAction?: CloseAction;
988
+ /**
989
+ * Where the native toolbar close button is placed.
990
+ *
991
+ * When omitted, each platform keeps its convention: Android places the close button at the
992
+ * start, iOS at the end (or at the start when `showArrow` is true). Setting this option
993
+ * overrides that placement on both platforms.
994
+ *
995
+ * @since 8.17.0
996
+ * @example
997
+ * closeButtonPosition: CloseButtonPosition.END
998
+ */
999
+ closeButtonPosition?: CloseButtonPosition;
978
1000
  /**
979
1001
  * Captures the visible webview and includes it as `screenshot` in `hideEvent`
980
1002
  * before the toolbar close button hides the webview.
@@ -48,4 +48,15 @@ export var CloseAction;
48
48
  */
49
49
  CloseAction["HIDE"] = "hide";
50
50
  })(CloseAction || (CloseAction = {}));
51
+ export var CloseButtonPosition;
52
+ (function (CloseButtonPosition) {
53
+ /**
54
+ * Leading edge of the toolbar (left in left-to-right layouts).
55
+ */
56
+ CloseButtonPosition["START"] = "start";
57
+ /**
58
+ * Trailing edge of the toolbar (right in left-to-right layouts).
59
+ */
60
+ CloseButtonPosition["END"] = "end";
61
+ })(CloseButtonPosition || (CloseButtonPosition = {}));
51
62
  //# sourceMappingURL=definitions.js.map