feat(input, textarea): ionInput and ionChange pass event and value (#26176)

BREAKING CHANGE:

The `detail` payload for the `ionInput` event on `ion-input` and `ion-textarea` now contains an object with the current `value` as well as the native event that triggered `ionInput`.
This commit is contained in:
Liam DeBeasi
2022-11-01 11:30:58 -05:00
committed by GitHub
parent 663750ec17
commit eea6ba996c
10 changed files with 68 additions and 39 deletions

View File

@@ -1,12 +1,16 @@
export interface InputChangeEventDetail {
value: string | undefined | null;
value?: string | number | null;
event?: Event;
}
// We recognize that InputInput is not an ideal naming pattern for this type.
// TODO (FW-2199): Explore renaming this type to something more appropriate.
export type InputInputEventDetail = InputEvent | Event;
export interface InputInputEventDetail {
value?: string | number | null;
event?: Event;
}
export interface InputCustomEvent extends CustomEvent {
detail: InputChangeEventDetail;
export interface InputCustomEvent<T = InputChangeEventDetail> extends CustomEvent {
detail: T;
target: HTMLIonInputElement;
}

View File

@@ -342,13 +342,21 @@ export class Input implements ComponentInterface {
* This API should be called for user committed changes.
* This API should not be used for external value changes.
*/
private emitValueChange() {
private emitValueChange(event?: Event) {
const { value } = this;
// Checks for both null and undefined values
const newValue = value == null ? value : value.toString();
// Emitting a value change should update the internal state for tracking the focused value
this.focusedValue = newValue;
this.ionChange.emit({ value: newValue });
this.ionChange.emit({ value: newValue, event });
}
/**
* Emits an `ionInput` event.
*/
private emitInputChange(event?: Event) {
const { value } = this;
this.ionInput.emit({ value, event });
}
private shouldClearOnEdit() {
@@ -376,11 +384,11 @@ export class Input implements ComponentInterface {
if (input) {
this.value = input.value || '';
}
this.ionInput.emit(ev as InputEvent);
this.emitInputChange(ev);
};
private onChange = () => {
this.emitValueChange();
private onChange = (ev: Event) => {
this.emitValueChange(ev);
};
private onBlur = (ev: FocusEvent) => {
@@ -392,7 +400,7 @@ export class Input implements ComponentInterface {
* Emits the `ionChange` event when the input value
* is different than the value when the input was focused.
*/
this.emitValueChange();
this.emitValueChange(ev);
}
this.didInputClearOnEdit = false;
@@ -422,7 +430,7 @@ export class Input implements ComponentInterface {
*/
if (!this.didInputClearOnEdit && this.hasValue() && ev.key !== 'Enter') {
this.value = '';
this.ionInput.emit();
this.emitInputChange(ev);
}
this.didInputClearOnEdit = true;
}
@@ -443,7 +451,7 @@ export class Input implements ComponentInterface {
this.setFocus();
}
this.value = '';
this.ionInput.emit(ev);
this.emitInputChange(ev);
};
private hasValue(): boolean {

View File

@@ -20,7 +20,7 @@ test.describe('input: events: ionChange', () => {
await ionChangeSpy.next();
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value' });
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value', event: { isTrusted: true } });
});
});
@@ -71,7 +71,7 @@ test.describe('input: events: ionInput', () => {
await page.click('ion-input .input-clear-icon');
expect(ionInputSpy).toHaveReceivedEventDetail({ isTrusted: true });
expect(ionInputSpy).toHaveReceivedEventDetail({ value: '', event: { isTrusted: true } });
});
test('should emit when the input is cleared from the keyboard', async ({ page }) => {
@@ -86,6 +86,6 @@ test.describe('input: events: ionInput', () => {
expect(await input.evaluate((el: HTMLIonInputElement) => el.value)).toBe('');
expect(ionInputSpy).toHaveReceivedEventTimes(1);
expect(ionInputSpy).toHaveReceivedEventDetail(null);
expect(ionInputSpy).toHaveReceivedEventDetail({ value: '', event: { isTrusted: true } });
});
});

View File

@@ -19,7 +19,7 @@ test.describe('textarea: events: ionChange', () => {
await ionChangeSpy.next();
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value' });
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value', event: { isTrusted: true } });
expect(ionChangeSpy).toHaveReceivedEventTimes(1);
});
@@ -36,7 +36,7 @@ test.describe('textarea: events: ionChange', () => {
await ionChangeSpy.next();
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value' });
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value', event: { isTrusted: true } });
expect(ionChangeSpy).toHaveReceivedEventTimes(1);
});
@@ -75,7 +75,7 @@ test.describe('textarea: events: ionInput', () => {
const nativeTextarea = page.locator('ion-textarea textarea');
await nativeTextarea.type('new value', { delay: 100 });
expect(ionInputSpy).toHaveReceivedEventDetail({ isTrusted: true });
expect(ionInputSpy).toHaveReceivedEventDetail({ value: 'new valuesome value', event: { isTrusted: true } });
});
test('should emit when the textarea is cleared on edit', async ({ page }) => {
@@ -88,6 +88,6 @@ test.describe('textarea: events: ionInput', () => {
await textarea.press('Backspace');
expect(ionInputSpy).toHaveReceivedEventTimes(1);
expect(ionInputSpy).toHaveReceivedEventDetail(null);
expect(ionInputSpy).toHaveReceivedEventDetail({ value: '', event: { isTrusted: true } });
});
});

View File

@@ -1,8 +1,14 @@
export interface TextareaChangeEventDetail {
value?: string | null;
event?: Event;
}
export interface TextareaCustomEvent extends CustomEvent {
detail: TextareaChangeEventDetail;
export interface TextareaInputEventDetail {
value?: string | null;
event?: Event;
}
export interface TextareaCustomEvent<T = TextareaChangeEventDetail> extends CustomEvent {
detail: T;
target: HTMLIonTextareaElement;
}

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h, writeTask } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import type { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
import type { Color, StyleEventDetail, TextareaChangeEventDetail, TextareaInputEventDetail } from '../../interface';
import type { Attributes } from '../../utils/helpers';
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
import { createColorClasses } from '../../utils/theme';
@@ -188,7 +188,7 @@ export class Textarea implements ComponentInterface {
* When `clearOnEdit` is enabled, the `ionInput` event will be fired when
* the user clears the textarea by performing a keydown event.
*/
@Event() ionInput!: EventEmitter<InputEvent>;
@Event() ionInput!: EventEmitter<TextareaInputEventDetail>;
/**
* Emitted when the styles change.
@@ -276,13 +276,21 @@ export class Textarea implements ComponentInterface {
* This API should be called for user committed changes.
* This API should not be used for external value changes.
*/
private emitValueChange() {
private emitValueChange(event?: Event) {
const { value } = this;
// Checks for both null and undefined values
const newValue = value == null ? value : value.toString();
// Emitting a value change should update the internal state for tracking the focused value
this.focusedValue = newValue;
this.ionChange.emit({ value: newValue });
this.ionChange.emit({ value: newValue, event });
}
/**
* Emits an `ionInput` event.
*/
private emitInputChange(event?: Event) {
const { value } = this;
this.ionInput.emit({ value, event });
}
private runAutoGrow() {
@@ -300,7 +308,7 @@ export class Textarea implements ComponentInterface {
/**
* Check if we need to clear the text input if clearOnEdit is enabled
*/
private checkClearOnEdit() {
private checkClearOnEdit(ev: Event) {
if (!this.clearOnEdit) {
return;
}
@@ -310,7 +318,7 @@ export class Textarea implements ComponentInterface {
*/
if (!this.didTextareaClearOnEdit && this.hasValue()) {
this.value = '';
this.ionInput.emit();
this.emitInputChange(ev);
}
this.didTextareaClearOnEdit = true;
}
@@ -337,11 +345,11 @@ export class Textarea implements ComponentInterface {
if (input) {
this.value = input.value || '';
}
this.ionInput.emit(ev as InputEvent);
this.emitInputChange(ev);
};
private onChange = () => {
this.emitValueChange();
private onChange = (ev: Event) => {
this.emitValueChange(ev);
};
private onFocus = (ev: FocusEvent) => {
@@ -361,14 +369,14 @@ export class Textarea implements ComponentInterface {
* Emits the `ionChange` event when the textarea value
* is different than the value when the textarea was focused.
*/
this.emitValueChange();
this.emitValueChange(ev);
}
this.didTextareaClearOnEdit = false;
this.ionBlur.emit(ev);
};
private onKeyDown = () => {
this.checkClearOnEdit();
private onKeyDown = (ev: Event) => {
this.checkClearOnEdit(ev);
};
render() {