fix(input): only focus the first input / textarea when clicking on the parent item (#22049)

fixes #22037 resolves #22032
This commit is contained in:
Brandy Carney
2020-09-14 11:44:37 -04:00
committed by GitHub
parent c72c7ffa98
commit 99f2532ee1
5 changed files with 91 additions and 22 deletions

View File

@ -2439,6 +2439,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"`. * 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'; "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 `<textarea>` element used under the hood. * Returns the native `<textarea>` element used under the hood.
*/ */
@ -2480,7 +2484,11 @@ export namespace Components {
*/ */
"rows"?: number; "rows"?: number;
/** /**
* Sets focus on the specified `ion-textarea`. Use this method instead of the global `input.focus()`. * Sets blur on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.blur()`.
*/
"setBlur": () => Promise<void>;
/**
* Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.focus()`.
*/ */
"setFocus": () => Promise<void>; "setFocus": () => Promise<void>;
/** /**
@ -5738,6 +5746,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"`. * 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'; "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"`. * A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`.
*/ */

View File

@ -177,9 +177,9 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
return (this.isClickable() || this.hasCover()); return (this.isClickable() || this.hasCover());
} }
private hasInputs(): boolean { private getFirstInput(): HTMLIonInputElement | HTMLIonTextareaElement {
const inputs = this.el.querySelectorAll('ion-input'); const inputs = this.el.querySelectorAll('ion-input, ion-textarea') as NodeListOf<HTMLIonInputElement | HTMLIonTextareaElement>;
return inputs.length > 0; return inputs[0];
} }
// This is needed for WebKit due to a delegatesFocus bug where // This is needed for WebKit due to a delegatesFocus bug where
@ -187,17 +187,27 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
// but is opening the keyboard. It will no longer be needed with // but is opening the keyboard. It will no longer be needed with
// iOS 14. // iOS 14.
@Listen('click') @Listen('click')
delegateFocus() { delegateFocus(ev: Event) {
if (this.hasInputs()) { const clickedItem = (ev.target as HTMLElement).tagName === 'ION-ITEM';
const input = this.el.querySelector('ion-input'); const input = this.getFirstInput();
if (input) { let firstActive = false;
input.fireFocusEvents = false;
input.setBlur(); // If the first input is the same as the active element we need
input.setFocus(); // to focus the first input again, but if the active element
raf(() => { // is another input inside of the item we shouldn't switch focus
input.fireFocusEvents = true; if (input && document.activeElement) {
}); firstActive = input.querySelector('input, textarea') === document.activeElement;
} }
// Only focus the first input if we clicked on an ion-item
// and the first input exists
if (clickedItem && input && firstActive) {
input.fireFocusEvents = false;
input.setBlur();
input.setFocus();
raf(() => {
input.fireFocusEvents = true;
});
} }
} }

View File

@ -123,6 +123,27 @@
</ion-label> </ion-label>
</ion-list-header> </ion-list-header>
<ion-list class="multiple"> <ion-list class="multiple">
<ion-item>
<ion-label>Multiple inputs</ion-label>
<ion-input placeholder="Input 1"></ion-input>
<ion-input placeholder="Input 2"></ion-input>
<ion-input placeholder="Input 3"></ion-input>
</ion-item>
<ion-item>
<ion-label>Multiple textareas</ion-label>
<ion-textarea placeholder="Textarea 1"></ion-textarea>
<ion-textarea placeholder="Textarea 2"></ion-textarea>
<ion-textarea placeholder="Textarea 3"></ion-textarea>
</ion-item>
<ion-item>
<ion-label>Multiple input/textareas</ion-label>
<ion-textarea placeholder="Textarea 1"></ion-textarea>
<ion-input placeholder="Input 2"></ion-input>
<ion-textarea placeholder="Textarea 3"></ion-textarea>
</ion-item>
<ion-item> <ion-item>
<ion-checkbox slot="start" id="checkbox-start"></ion-checkbox> <ion-checkbox slot="start" id="checkbox-start"></ion-checkbox>
<ion-label>Multiple inputs w/ cover</ion-label> <ion-label>Multiple inputs w/ cover</ion-label>
@ -179,7 +200,7 @@
clickableItem.color = color === undefined ? 'primary' : undefined; clickableItem.color = color === undefined ? 'primary' : undefined;
}); });
const inputs = document.querySelectorAll('ion-input'); const inputs = document.querySelectorAll('ion-input, ion-textarea');
for (var i = 0; i < inputs.length; i++) { for (var i = 0; i < inputs.length; i++) {
const input = inputs[i]; const input = inputs[i];

View File

@ -315,8 +315,8 @@ Type: `Promise<HTMLTextAreaElement>`
### `setFocus() => Promise<void>` ### `setFocus() => Promise<void>`
Sets focus on the specified `ion-textarea`. Use this method instead of the global Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global
`input.focus()`. `textarea.focus()`.
#### Returns #### Returns

View File

@ -23,6 +23,16 @@ export class Textarea implements ComponentInterface {
private didBlurAfterEdit = false; private didBlurAfterEdit = false;
private textareaWrapper?: HTMLElement; private textareaWrapper?: HTMLElement;
/**
* 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;
@Element() el!: HTMLElement; @Element() el!: HTMLElement;
@State() hasFocus = false; @State() hasFocus = false;
@ -220,8 +230,8 @@ export class Textarea implements ComponentInterface {
} }
/** /**
* Sets focus on the specified `ion-textarea`. Use this method instead of the global * Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global
* `input.focus()`. * `textarea.focus()`.
*/ */
@Method() @Method()
async setFocus() { async setFocus() {
@ -230,6 +240,18 @@ export class Textarea implements ComponentInterface {
} }
} }
/**
* Sets blur on the native `textarea` in `ion-textarea`. Use this method instead of the global
* `textarea.blur()`.
* @internal
*/
@Method()
async setBlur() {
if (this.nativeInput) {
this.nativeInput.blur();
}
}
/** /**
* Returns the native `<textarea>` element used under the hood. * Returns the native `<textarea>` element used under the hood.
*/ */
@ -296,14 +318,18 @@ export class Textarea implements ComponentInterface {
this.hasFocus = true; this.hasFocus = true;
this.focusChange(); this.focusChange();
this.ionFocus.emit(ev); if (this.fireFocusEvents) {
this.ionFocus.emit(ev);
}
} }
private onBlur = (ev: FocusEvent) => { private onBlur = (ev: FocusEvent) => {
this.hasFocus = false; this.hasFocus = false;
this.focusChange(); this.focusChange();
this.ionBlur.emit(ev); if (this.fireFocusEvents) {
this.ionBlur.emit(ev);
}
} }
private onKeyDown = () => { private onKeyDown = () => {