From e4964ff77b317c92b201cf7c265787b55bdde4d4 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Thu, 27 Aug 2020 14:52:02 -0400 Subject: [PATCH] fix(input): properly focus the input when clicking the item padding in WebKit (#21930) Related WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=214859 Issue Number: fixes #21509 --- core/src/components.d.ts | 14 +++++++- core/src/components/input/input.tsx | 32 +++++++++++++++++-- core/src/components/input/readme.md | 2 +- .../components/input/test/tabindex/index.html | 6 ++++ core/src/components/item/item.tsx | 25 +++++++++++++++ .../components/item/test/inputs/index.html | 13 ++++++++ 6 files changed, 87 insertions(+), 5 deletions(-) diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 045812b6c7..b3878a8093 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -869,6 +869,10 @@ export namespace Components { * A hint to the browser for which enter key to display. Possible values: `"enter"`, `"done"`, `"go"`, `"next"`, `"previous"`, `"search"`, and `"send"`. */ "enterkeyhint"?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + /** + * This is required for a WebKit bug which requires us to blur and focus an input to properly focus the input in an item with delegatesFocus. It will no longer be needed with iOS 14. + */ + "fireFocusEvents": boolean; /** * Returns the native `` element used under the hood. */ @@ -922,7 +926,11 @@ export namespace Components { */ "required": boolean; /** - * Sets focus on the specified `ion-input`. Use this method instead of the global `input.focus()`. + * Sets blur on the native `input` in `ion-input`. Use this method instead of the global `input.blur()`. + */ + "setBlur": () => Promise; + /** + * Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. */ "setFocus": () => Promise; /** @@ -4190,6 +4198,10 @@ declare namespace LocalJSX { * A hint to the browser for which enter key to display. Possible values: `"enter"`, `"done"`, `"go"`, `"next"`, `"previous"`, `"search"`, and `"send"`. */ "enterkeyhint"?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + /** + * This is required for a WebKit bug which requires us to blur and focus an input to properly focus the input in an item with delegatesFocus. It will no longer be needed with iOS 14. + */ + "fireFocusEvents"?: boolean; /** * A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. */ diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 261fbaadc4..6d55057083 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -23,6 +23,16 @@ export class Input implements ComponentInterface { private didBlurAfterEdit = false; private tabindex?: string | number; + /** + * This is required for a WebKit bug which requires us to + * blur and focus an input to properly focus the input in + * an item with delegatesFocus. It will no longer be needed + * with iOS 14. + * + * @internal + */ + @Prop() fireFocusEvents = true; + @State() hasFocus = false; @Element() el!: HTMLElement; @@ -244,7 +254,7 @@ export class Input implements ComponentInterface { } /** - * Sets focus on the specified `ion-input`. Use this method instead of the global + * Sets focus on the native `input` in `ion-input`. Use this method instead of the global * `input.focus()`. */ @Method() @@ -254,6 +264,18 @@ export class Input implements ComponentInterface { } } + /** + * Sets blur on the native `input` in `ion-input`. Use this method instead of the global + * `input.blur()`. + * @internal + */ + @Method() + async setBlur() { + if (this.nativeInput) { + this.nativeInput.blur(); + } + } + /** * Returns the native `` element used under the hood. */ @@ -298,7 +320,9 @@ export class Input implements ComponentInterface { this.focusChanged(); this.emitStyle(); - this.ionBlur.emit(ev); + if (this.fireFocusEvents) { + this.ionBlur.emit(ev); + } } private onFocus = (ev: FocusEvent) => { @@ -306,7 +330,9 @@ export class Input implements ComponentInterface { this.focusChanged(); this.emitStyle(); - this.ionFocus.emit(ev); + if (this.fireFocusEvents) { + this.ionFocus.emit(ev); + } } private onKeydown = (ev: KeyboardEvent) => { diff --git a/core/src/components/input/readme.md b/core/src/components/input/readme.md index 015cf211d7..b54fb9d80a 100644 --- a/core/src/components/input/readme.md +++ b/core/src/components/input/readme.md @@ -345,7 +345,7 @@ Type: `Promise` ### `setFocus() => Promise` -Sets focus on the specified `ion-input`. Use this method instead of the global +Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. #### Returns diff --git a/core/src/components/input/test/tabindex/index.html b/core/src/components/input/test/tabindex/index.html index 9a6223e722..3c569bc1d5 100644 --- a/core/src/components/input/test/tabindex/index.html +++ b/core/src/components/input/test/tabindex/index.html @@ -57,6 +57,7 @@ Tab 1st + @@ -69,6 +70,11 @@ .md div { padding-left: 8px; } + + ion-item { + --background: #f6f6f6; + --padding-start: 40px; + } diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx index a60ed53346..25d90f9835 100644 --- a/core/src/components/item/item.tsx +++ b/core/src/components/item/item.tsx @@ -3,6 +3,7 @@ import { Component, ComponentInterface, Element, Host, Listen, Prop, State, forc import { getIonMode } from '../../global/ionic-global'; import { AnimationBuilder, Color, CssClassMap, RouterDirection, StyleEventDetail } from '../../interface'; import { AnchorInterface, ButtonInterface } from '../../utils/element-interface'; +import { raf } from '../../utils/helpers'; import { createColorClasses, hostContext, openURL } from '../../utils/theme'; /** @@ -176,6 +177,30 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac return (this.isClickable() || this.hasCover()); } + private hasInputs(): boolean { + const inputs = this.el.querySelectorAll('ion-input'); + return inputs.length > 0; + } + + // This is needed for WebKit due to a delegatesFocus bug where + // clicking on the left padding of an item is not focusing the input + // but is opening the keyboard. It will no longer be needed with + // iOS 14. + @Listen('click') + delegateFocus() { + if (this.hasInputs()) { + const input = this.el.querySelector('ion-input'); + if (input) { + input.fireFocusEvents = false; + input.setBlur(); + input.setFocus(); + raf(() => { + input.fireFocusEvents = true; + }); + } + } + } + render() { const { detail, detailIcon, download, lines, disabled, href, rel, target, routerAnimation, routerDirection } = this; const childStyles = {}; diff --git a/core/src/components/item/test/inputs/index.html b/core/src/components/item/test/inputs/index.html index c5e39da0d4..a11e1f0461 100644 --- a/core/src/components/item/test/inputs/index.html +++ b/core/src/components/item/test/inputs/index.html @@ -179,6 +179,19 @@ clickableItem.color = color === undefined ? 'primary' : undefined; }); + const inputs = document.querySelectorAll('ion-input'); + + for (var i = 0; i < inputs.length; i++) { + const input = inputs[i]; + + input.addEventListener('ionBlur', function() { + console.log('Listen ionBlur: fired'); + }); + + input.addEventListener('ionFocus', function() { + console.log('Listen ionFocus: fired'); + }); + } function toggleDisabled() { isDisabled = !isDisabled;