diff --git a/BREAKING.md b/BREAKING.md
index a7ac8778e8..9eb81a1721 100644
--- a/BREAKING.md
+++ b/BREAKING.md
@@ -96,6 +96,8 @@ This section details the desktop browser, JavaScript framework, and mobile platf
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
+- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
+
Modal
- The `swipeToClose` property has been removed in favor of `canDismiss`.
@@ -168,7 +170,7 @@ Developers using these components will need to migrate to using Swiper.js direct
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
-- `ionInput` dispatches an event detail of `null` when the textarea is cleared as a result of `clear-on-edit="true"`.
+- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
Toggle
diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts
index c72ceeb3a2..19948ca2d9 100644
--- a/angular/src/directives/proxies.ts
+++ b/angular/src/directives/proxies.ts
@@ -1832,6 +1832,7 @@ export class IonText {
}
import type { TextareaChangeEventDetail as ITextareaTextareaChangeEventDetail } from '@ionic/core';
+import type { TextareaInputEventDetail as ITextareaTextareaInputEventDetail } from '@ionic/core';
export declare interface IonTextarea extends Components.IonTextarea {
/**
* The `ionChange` event is fired for `` elements when the user
@@ -1849,7 +1850,7 @@ has been changed.
When `clearOnEdit` is enabled, the `ionInput` event will be fired when
the user clears the textarea by performing a keydown event.
*/
- ionInput: EventEmitter>;
+ ionInput: EventEmitter>;
/**
* Emitted when the input loses focus.
*/
diff --git a/core/api.txt b/core/api.txt
index 17fd18ae3c..d40de6fa3c 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -552,7 +552,7 @@ ion-input,method,setFocus,setFocus() => Promise
ion-input,event,ionBlur,FocusEvent,true
ion-input,event,ionChange,InputChangeEventDetail,true
ion-input,event,ionFocus,FocusEvent,true
-ion-input,event,ionInput,Event | InputEvent,true
+ion-input,event,ionInput,InputInputEventDetail,true
ion-input,css-prop,--background
ion-input,css-prop,--color
ion-input,css-prop,--padding-bottom
@@ -1315,7 +1315,7 @@ ion-textarea,method,setFocus,setFocus() => Promise
ion-textarea,event,ionBlur,FocusEvent,true
ion-textarea,event,ionChange,TextareaChangeEventDetail,true
ion-textarea,event,ionFocus,FocusEvent,true
-ion-textarea,event,ionInput,InputEvent,true
+ion-textarea,event,ionInput,TextareaInputEventDetail,true
ion-textarea,css-prop,--background
ion-textarea,css-prop,--border-radius
ion-textarea,css-prop,--color
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index 27d3d7405e..12393dbc93 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -5,7 +5,7 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
-import { AccordionGroupChangeEventDetail, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimePresentation, FrameworkDelegate, InputChangeEventDetail, InputInputEventDetail, ItemReorderEventDetail, MenuChangeEventDetail, ModalBreakpointChangeEventDetail, ModalHandleBehavior, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerButton, PickerColumn, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, TitleSelectedDatesFormatter, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface";
+import { AccordionGroupChangeEventDetail, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimePresentation, FrameworkDelegate, InputChangeEventDetail, InputInputEventDetail, ItemReorderEventDetail, MenuChangeEventDetail, ModalBreakpointChangeEventDetail, ModalHandleBehavior, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerButton, PickerColumn, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextareaInputEventDetail, TextFieldTypes, TitleSelectedDatesFormatter, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface";
import { IonicSafeString } from "./utils/sanitization";
import { CounterFormatter } from "./components/item/item-interface";
import { PickerColumnItem } from "./components/picker-column-internal/picker-column-internal-interfaces";
@@ -6624,7 +6624,7 @@ declare namespace LocalJSX {
/**
* The `ionInput` event fires when the `value` of an `` element has been changed. When `clearOnEdit` is enabled, the `ionInput` event will be fired when the user clears the textarea by performing a keydown event.
*/
- "onIonInput"?: (event: IonTextareaCustomEvent) => void;
+ "onIonInput"?: (event: IonTextareaCustomEvent) => void;
/**
* Emitted when the styles change.
*/
diff --git a/core/src/components/input/input-interface.ts b/core/src/components/input/input-interface.ts
index 1e2f865e4b..6b87d102a4 100644
--- a/core/src/components/input/input-interface.ts
+++ b/core/src/components/input/input-interface.ts
@@ -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 extends CustomEvent {
+ detail: T;
target: HTMLIonInputElement;
}
diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx
index 528871a9a5..045b955f6c 100644
--- a/core/src/components/input/input.tsx
+++ b/core/src/components/input/input.tsx
@@ -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 {
diff --git a/core/src/components/input/test/input-events.e2e.ts b/core/src/components/input/test/input-events.e2e.ts
index b56cb8298f..30a37e7e4f 100644
--- a/core/src/components/input/test/input-events.e2e.ts
+++ b/core/src/components/input/test/input-events.e2e.ts
@@ -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 } });
});
});
diff --git a/core/src/components/textarea/test/textarea-events.e2e.ts b/core/src/components/textarea/test/textarea-events.e2e.ts
index fefb472031..801e38c2b2 100644
--- a/core/src/components/textarea/test/textarea-events.e2e.ts
+++ b/core/src/components/textarea/test/textarea-events.e2e.ts
@@ -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 } });
});
});
diff --git a/core/src/components/textarea/textarea-interface.ts b/core/src/components/textarea/textarea-interface.ts
index a3f79c5aa1..2b97999db4 100644
--- a/core/src/components/textarea/textarea-interface.ts
+++ b/core/src/components/textarea/textarea-interface.ts
@@ -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 extends CustomEvent {
+ detail: T;
target: HTMLIonTextareaElement;
}
diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx
index bb6910db8a..c4350b8da8 100644
--- a/core/src/components/textarea/textarea.tsx
+++ b/core/src/components/textarea/textarea.tsx
@@ -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;
+ @Event() ionInput!: EventEmitter;
/**
* 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() {