diff --git a/android/src/main/java/com/capacitorjs/plugins/keyboard/KeyboardPlugin.java b/android/src/main/java/com/capacitorjs/plugins/keyboard/KeyboardPlugin.java index bf6735a..40fe0b7 100644 --- a/android/src/main/java/com/capacitorjs/plugins/keyboard/KeyboardPlugin.java +++ b/android/src/main/java/com/capacitorjs/plugins/keyboard/KeyboardPlugin.java @@ -26,13 +26,10 @@ public void load() { @PluginMethod public void show(final PluginCall call) { execute(() -> - new Handler(Looper.getMainLooper()).postDelayed( - () -> { - implementation.show(); - call.resolve(); - }, - 350 - ) + new Handler(Looper.getMainLooper()).postDelayed(() -> { + implementation.show(); + call.resolve(); + }, 350) ); } diff --git a/example-app/capacitor.config.ts b/example-app/capacitor.config.ts index f7981af..a139aed 100644 --- a/example-app/capacitor.config.ts +++ b/example-app/capacitor.config.ts @@ -3,7 +3,13 @@ import type { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { appId: 'io.ionic.starter', appName: 'CAP-KEYB-SAMPLE', - webDir: 'dist' + webDir: 'dist', + backgroundColor: "#fffb38", + plugins: { + Keyboard: { + autoBackdropColor: 'auto' + } + } }; export default config; diff --git a/ios/Sources/KeyboardPlugin/Keyboard.m b/ios/Sources/KeyboardPlugin/Keyboard.m index 29c0708..54a6694 100644 --- a/ios/Sources/KeyboardPlugin/Keyboard.m +++ b/ios/Sources/KeyboardPlugin/Keyboard.m @@ -18,6 +18,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import "Keyboard.h" #import #import +#import #import #import #import @@ -54,6 +55,105 @@ @implementation KeyboardPlugin NSString* UITraitsClassString; double stageManagerOffset; +#pragma mark - Helpers + +- (UIWindow *)currentKeyWindow { + UIWindow *window = nil; + if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) { + window = [[[UIApplication sharedApplication] delegate] window]; + } + if (!window) { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", UIWindowScene.class]; + UIScene *scene = [UIApplication.sharedApplication.connectedScenes.allObjects filteredArrayUsingPredicate:predicate].firstObject; + window = [[(UIWindowScene*)scene windows] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isKeyWindow == YES"]].firstObject; + } + return window; +} + +- (void)forceBackdropColor:(UIColor *)color { + UIWindow *w = [self currentKeyWindow]; + if (w) { + dispatch_async(dispatch_get_main_queue(), ^{ + w.backgroundColor = color; + }); + } +} + +- (BOOL)isIPad { + return ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad); +} + +- (BOOL)shouldIgnoreResizeForHeight:(double)height { + if (![self isIPad]) return NO; + if (height <= 0.0) return NO; + CGFloat screenHeight = UIScreen.mainScreen.bounds.size.height; + return (height / screenHeight) < 0.20; +} + +#pragma mark - Lifecycle + +- (UIColor *)colorFromCssColorString:(NSString *)cssColor { + NSString *trimmed = [cssColor stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if ([trimmed isEqualToString:@"transparent"]) { + return [UIColor clearColor]; + } + + if ([trimmed hasPrefix:@"rgb"]) { + NSRange parenRange = [trimmed rangeOfString:@"("]; + NSString *clean = parenRange.location != NSNotFound ? [trimmed substringFromIndex:parenRange.location + 1] : trimmed; + clean = [clean stringByReplacingOccurrencesOfString:@")" withString:@""]; + NSArray *parts = [clean componentsSeparatedByString:@","]; + if (parts.count >= 3) { + CGFloat r = [parts[0] floatValue] / 255.0; + CGFloat g = [parts[1] floatValue] / 255.0; + CGFloat b = [parts[2] floatValue] / 255.0; + CGFloat a = parts.count >= 4 ? [parts[3] floatValue] : 1.0; + return [UIColor colorWithRed:r green:g blue:b alpha:a]; + } + } + + if ([trimmed hasPrefix:@"#"]) { + unsigned rgbValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:trimmed]; + [scanner setScanLocation:1]; + if ([scanner scanHexInt:&rgbValue]) { + CGFloat r = ((rgbValue & 0xFF0000) >> 16) / 255.0; + CGFloat g = ((rgbValue & 0x00FF00) >> 8) / 255.0; + CGFloat b = (rgbValue & 0x0000FF) / 255.0; + return [UIColor colorWithRed:r green:g blue:b alpha:1.0]; + } + } + + return [UIColor whiteColor]; // fallback +} + +- (void)updateBackdropColor { + NSString *mode = [[self getConfig] getString:@"autoBackdropColor": @"off"]; + + if ([mode isEqualToString:@"auto"]) { + if (self.bridge.config.backgroundColor) { + [self forceBackdropColor:self.bridge.config.backgroundColor]; + } else { + [self updateBackdropColorFromDOM]; + } + } else if ([mode isEqualToString:@"dom"]) { + [self updateBackdropColorFromDOM]; + } +} + +- (void)updateBackdropColorFromDOM { + if (!self.webView) return; + [self.webView evaluateJavaScript:@"window.getComputedStyle(document.body).backgroundColor" completionHandler:^(id result, NSError *error) { + if (result && [result isKindOfClass:[NSString class]]) { + UIColor *color = [self colorFromCssColorString:(NSString *)result]; + if (color) { + [self forceBackdropColor:color]; + } + } + }]; +} + - (void)load { self.disableScroll = !self.bridge.config.scrollingEnabled; @@ -97,8 +197,9 @@ - (void)load [nc removeObserver:self.webView name:UIKeyboardWillShowNotification object:nil]; [nc removeObserver:self.webView name:UIKeyboardWillChangeFrameNotification object:nil]; [nc removeObserver:self.webView name:UIKeyboardDidChangeFrameNotification object:nil]; -} + [self updateBackdropColor]; +} #pragma mark Keyboard events @@ -124,11 +225,15 @@ - (void)onKeyboardWillShow:(NSNotification *)notification if (hideTimer != nil) { [hideTimer invalidate]; } + + // Force DOM color whenever keyboard shows + [self updateBackdropColor]; + CGRect rect = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; double height = rect.size.height; - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + if ([self isIPad]) { if (stageManagerOffset > 0) { height = stageManagerOffset; } else { @@ -142,8 +247,14 @@ - (void)onKeyboardWillShow:(NSNotification *)notification } } + BOOL ignored = [self shouldIgnoreResizeForHeight:height]; + if (ignored) { + NSLog(@"KeyboardPlugin: Ignoring QuickType Bar (%.1f) -> treat as 0.", height); + height = 0.0; + } + double duration = [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]+0.2; - [self setKeyboardHeight:height delay:duration]; + [self setKeyboardHeight:(int)height delay:duration]; [self resetScrollView]; NSString * data = [NSString stringWithFormat:@"{ 'keyboardHeight': %d }", (int)height]; @@ -157,6 +268,24 @@ - (void)onKeyboardDidShow:(NSNotification *)notification CGRect rect = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; double height = rect.size.height; + if ([self isIPad]) { + if (stageManagerOffset > 0) { + height = stageManagerOffset; + } else { + CGRect webViewAbsolute = [self.webView convertRect:self.webView.frame toCoordinateSpace:self.webView.window.screen.coordinateSpace]; + height = (webViewAbsolute.size.height + webViewAbsolute.origin.y) - (UIScreen.mainScreen.bounds.size.height - rect.size.height); + if (height < 0) { + height = 0; + } + } + } + + BOOL ignored = [self shouldIgnoreResizeForHeight:height]; + if (ignored) { + NSLog(@"KeyboardPlugin: (didShow) Ignoring QuickType Bar (%.1f) -> report 0.", height); + height = 0.0; + } + [self resetScrollView]; NSString * data = [NSString stringWithFormat:@"{ 'keyboardHeight': %d }", (int)height]; @@ -205,19 +334,8 @@ - (void)resizeElement:(NSString *)element withPaddingBottom:(int)paddingBottom w - (void)_updateFrame { CGRect f, wf = CGRectZero; - UIWindow * window = nil; - - if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) { - window = [[[UIApplication sharedApplication] delegate] window]; - } - - if (!window) { - if (@available(iOS 13.0, *)) { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", UIWindowScene.class]; - UIScene *scene = [UIApplication.sharedApplication.connectedScenes.allObjects filteredArrayUsingPredicate:predicate].firstObject; - window = [[(UIWindowScene*)scene windows] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isKeyWindow == YES"]].firstObject; - } - } + UIWindow *window = [self currentKeyWindow]; + if (window) { f = [window bounds]; } diff --git a/src/definitions.ts b/src/definitions.ts index c0c98b9..30968dd 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -41,6 +41,25 @@ declare module '@capacitor/cli' { * @example true */ resizeOnFullScreen?: boolean; + + /** + * Controls how the keyboard backdrop color (the area visible behind the + * keyboard) is set every time the keyboard is about to show. + * + * - `'off'` — Do not tint the backdrop. + * - `'auto'` — Use the `backgroundColor` set in the Capacitor config; + * otherwise derive the color from the web app's DOM body background. + * - `'dom'` — Always derive the color from the web app's DOM body + * background, ignoring the `backgroundColor` config. If the DOM + * has no resolvable background, the backdrop is left untouched. + * + * Only available on iOS. + * + * @since 8.1.0 + * @default "off" + * @example "auto" + */ + autoBackdropColor?: 'off' | 'auto' | 'dom'; }; } }