feat(inputs) debounce input and change events (#13764)

* feat(helpers) add debounce helper

* feat(searchbar) debounce ionInput

* feat(range) debounce ionChange

* feat(input) debouce ionInput

* feat(textarea) debounce ionInput

* feat(range) make debounceChange private

* feat(searchbar) make debounceInput private

* feat(inputs) change default debounce to 0 in input/textarea
This commit is contained in:
Cam Wiegert
2018-01-04 15:07:50 -06:00
committed by GitHub
parent 0273cb2a20
commit 7f8cf42773
8 changed files with 109 additions and 16 deletions

View File

@ -1155,6 +1155,7 @@ declare global {
checked?: boolean; checked?: boolean;
clearInput?: boolean; clearInput?: boolean;
clearOnEdit?: boolean; clearOnEdit?: boolean;
debounce?: number;
disabled?: boolean; disabled?: boolean;
inputmode?: string; inputmode?: string;
max?: string; max?: string;
@ -2880,6 +2881,7 @@ declare global {
autofocus?: boolean; autofocus?: boolean;
clearOnEdit?: boolean; clearOnEdit?: boolean;
cols?: number; cols?: number;
debounce?: number;
disabled?: boolean; disabled?: boolean;
maxlength?: number; maxlength?: number;
minlength?: number; minlength?: number;

View File

@ -1,5 +1,6 @@
import { Component, Element, Event, EventEmitter, Prop, PropDidChange } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Prop, PropDidChange } from '@stencil/core';
import { debounce } from '../../utils/helpers';
import { createThemedClasses } from '../../utils/theme'; import { createThemedClasses } from '../../utils/theme';
import { InputComponent } from './input-base'; import { InputComponent } from './input-base';
@ -23,6 +24,11 @@ export class Input implements InputComponent {
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
/**
* @output {Event} Emitted when the input value has changed.
*/
@Event() ionInput: EventEmitter;
/** /**
* @output {Event} Emitted when the styles change. * @output {Event} Emitted when the styles change.
*/ */
@ -83,6 +89,18 @@ export class Input implements InputComponent {
*/ */
@Prop({ mutable: true }) clearOnEdit: boolean; @Prop({ mutable: true }) clearOnEdit: boolean;
/**
* @input {number} Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke. Default `0`.
*/
@Prop() debounce: number = 0;
@PropDidChange('debounce')
private debounceInput() {
this.ionInput.emit = debounce(
this.ionInput.emit.bind(this.ionInput),
this.debounce
);
}
/** /**
* @input {boolean} If true, the user cannot interact with the input. Defaults to `false`. * @input {boolean} If true, the user cannot interact with the input. Defaults to `false`.
*/ */
@ -192,6 +210,7 @@ export class Input implements InputComponent {
componentDidLoad() { componentDidLoad() {
this.debounceInput();
this.emitStyle(); this.emitStyle();
// By default, password inputs clear after focus when they have content // By default, password inputs clear after focus when they have content
@ -225,6 +244,7 @@ export class Input implements InputComponent {
inputChanged(ev: any) { inputChanged(ev: any) {
this.value = ev.target && ev.target.value; this.value = ev.target && ev.target.value;
this.ionInput.emit(ev);
this.emitStyle(); this.emitStyle();
} }
@ -242,15 +262,15 @@ export class Input implements InputComponent {
} }
} }
inputKeydown() { inputKeydown(ev: any) {
this.checkClearOnEdit(); this.checkClearOnEdit(ev);
} }
/** /**
* Check if we need to clear the text input if clearOnEdit is enabled * Check if we need to clear the text input if clearOnEdit is enabled
*/ */
checkClearOnEdit() { checkClearOnEdit(ev: any) {
if (!this.clearOnEdit) { if (!this.clearOnEdit) {
return; return;
} }
@ -258,15 +278,16 @@ export class Input implements InputComponent {
// Did the input value change after it was blurred and edited? // Did the input value change after it was blurred and edited?
if (this.didBlurAfterEdit && this.hasValue()) { if (this.didBlurAfterEdit && this.hasValue()) {
// Clear the input // Clear the input
this.clearTextInput(); this.clearTextInput(ev);
} }
// Reset the flag // Reset the flag
this.didBlurAfterEdit = false; this.didBlurAfterEdit = false;
} }
clearTextInput() { clearTextInput(ev: any) {
this.value = ''; this.value = '';
this.ionInput.emit(ev);
} }
hasFocus(): boolean { hasFocus(): boolean {

View File

@ -47,6 +47,11 @@ boolean
boolean boolean
#### debounce
number
#### disabled #### disabled
boolean boolean
@ -179,6 +184,11 @@ boolean
boolean boolean
#### debounce
number
#### disabled #### disabled
boolean boolean
@ -277,6 +287,9 @@ string
#### ionFocus #### ionFocus
#### ionInput
#### ionStyle #### ionStyle

View File

@ -1,6 +1,6 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, PropDidChange, State } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Method, Prop, PropDidChange, State } from '@stencil/core';
import { BaseInputComponent, GestureDetail } from '../../index'; import { BaseInputComponent, GestureDetail } from '../../index';
import { clamp } from '../../utils/helpers'; import { clamp, debounce } from '../../utils/helpers';
@Component({ @Component({
tag: 'ion-range', tag: 'ion-range',
@ -75,6 +75,13 @@ export class Range implements BaseInputComponent {
* `ionChange` event after each change in the range value. Default `0`. * `ionChange` event after each change in the range value. Default `0`.
*/ */
@Prop() debounce: number = 0; @Prop() debounce: number = 0;
@PropDidChange('debounce')
private debounceChange() {
this.ionChange.emit = debounce(
this.ionChange.emit.bind(this.ionChange),
this.debounce
);
}
/* /*
* @input {boolean} If true, the user cannot interact with the range. Default false. * @input {boolean} If true, the user cannot interact with the range. Default false.
@ -126,13 +133,14 @@ export class Range implements BaseInputComponent {
@PropDidChange('value') @PropDidChange('value')
protected valueChanged(val: boolean) { protected valueChanged(val: boolean) {
setTimeout(() => this.ionChange.emit({value: val}), this.debounce); this.ionChange.emit({value: val});
this.emitStyle(); this.emitStyle();
} }
componentWillLoad() { componentWillLoad() {
this.inputUpdated(); this.inputUpdated();
this.createTicks(); this.createTicks();
this.debounceChange();
this.emitStyle(); this.emitStyle();
} }

View File

@ -1,4 +1,5 @@
import { Component, Element, Event, EventEmitter, Prop, State } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Prop, PropDidChange, State } from '@stencil/core';
import { debounce } from '../../utils/helpers';
@Component({ @Component({
@ -86,6 +87,13 @@ export class Searchbar {
* @input {number} Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke. Default `250`. * @input {number} Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke. Default `250`.
*/ */
@Prop({ mutable: true }) debounce: number = 250; @Prop({ mutable: true }) debounce: number = 250;
@PropDidChange('debounce')
private debounceInput() {
this.ionInput.emit = debounce(
this.ionInput.emit.bind(this.ionInput),
this.debounce
);
}
/** /**
* @input {string} Set the input's placeholder. Default `"Search"`. * @input {string} Set the input's placeholder. Default `"Search"`.
@ -115,6 +123,7 @@ export class Searchbar {
componentDidLoad() { componentDidLoad() {
this.positionElements(); this.positionElements();
this.debounceInput();
} }
/** /**
@ -153,9 +162,7 @@ export class Searchbar {
*/ */
inputChanged(ev: any) { inputChanged(ev: any) {
this.value = ev.target.value; this.value = ev.target.value;
setTimeout(() => {
this.ionInput.emit(ev); this.ionInput.emit(ev);
}, this.debounce);
} }
inputUpdated() { inputUpdated() {

View File

@ -66,6 +66,11 @@ boolean
number number
#### debounce
number
#### disabled #### disabled
boolean boolean
@ -148,6 +153,11 @@ boolean
number number
#### debounce
number
#### disabled #### disabled
boolean boolean
@ -211,6 +221,9 @@ string
#### ionFocus #### ionFocus
#### ionInput
#### ionStyle #### ionStyle

View File

@ -1,5 +1,6 @@
import { Component, Element, Event, EventEmitter, Prop, PropDidChange } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Prop, PropDidChange } from '@stencil/core';
import { debounce } from '../../utils/helpers';
import { createThemedClasses } from '../../utils/theme'; import { createThemedClasses } from '../../utils/theme';
import { TextareaComponent } from '../input/input-base'; import { TextareaComponent } from '../input/input-base';
@ -29,6 +30,11 @@ export class Textarea implements TextareaComponent {
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
/**
* @output {Event} Emitted when the input value has changed.
*/
@Event() ionInput: EventEmitter;
/** /**
* @output {Event} Emitted when the styles change. * @output {Event} Emitted when the styles change.
*/ */
@ -64,6 +70,18 @@ export class Textarea implements TextareaComponent {
*/ */
@Prop({ mutable: true }) clearOnEdit: boolean; @Prop({ mutable: true }) clearOnEdit: boolean;
/**
* @input {number} Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke. Default `0`.
*/
@Prop() debounce: number = 0;
@PropDidChange('debounce')
private debounceInput() {
this.ionInput.emit = debounce(
this.ionInput.emit.bind(this.ionInput),
this.debounce
);
}
/** /**
* @input {boolean} If true, the user cannot interact with the textarea. Defaults to `false`. * @input {boolean} If true, the user cannot interact with the textarea. Defaults to `false`.
*/ */
@ -141,6 +159,7 @@ export class Textarea implements TextareaComponent {
} }
componentDidLoad() { componentDidLoad() {
this.debounceInput();
this.emitStyle(); this.emitStyle();
} }
@ -160,8 +179,9 @@ export class Textarea implements TextareaComponent {
}); });
} }
clearTextInput() { clearTextInput(ev: any) {
this.value = ''; this.value = '';
this.ionInput.emit(ev);
} }
inputBlurred(ev: any) { inputBlurred(ev: any) {
@ -173,6 +193,7 @@ export class Textarea implements TextareaComponent {
inputChanged(ev: any) { inputChanged(ev: any) {
this.value = ev.target && ev.target.value; this.value = ev.target && ev.target.value;
this.ionInput.emit(ev);
this.emitStyle(); this.emitStyle();
} }
@ -183,14 +204,14 @@ export class Textarea implements TextareaComponent {
this.emitStyle(); this.emitStyle();
} }
inputKeydown() { inputKeydown(ev: any) {
this.checkClearOnEdit(); this.checkClearOnEdit(ev);
} }
/** /**
* Check if we need to clear the text input if clearOnEdit is enabled * Check if we need to clear the text input if clearOnEdit is enabled
*/ */
checkClearOnEdit() { checkClearOnEdit(ev: any) {
if (!this.clearOnEdit) { if (!this.clearOnEdit) {
return; return;
} }
@ -198,7 +219,7 @@ export class Textarea implements TextareaComponent {
// Did the input value change after it was blurred and edited? // Did the input value change after it was blurred and edited?
if (this.didBlurAfterEdit && this.hasValue()) { if (this.didBlurAfterEdit && this.hasValue()) {
// Clear the input // Clear the input
this.clearTextInput(); this.clearTextInput(ev);
} }
// Reset the flag // Reset the flag

View File

@ -295,3 +295,11 @@ export function domControllerAsync(domControllerFunction: Function, callback?: F
}); });
}); });
} }
export function debounce(func: Function, wait: number = 250) {
let timer: number;
return (...args: any[]): void => {
clearTimeout(timer);
timer = setTimeout(func, wait, ...args);
};
}