diff --git a/BREAKING.md b/BREAKING.md index 2ab8bb5226..c146937e11 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -123,7 +123,7 @@ This section details the desktop browser, JavaScript framework, and mobile platf

Input

-- `ionChange` is no longer emitted when the `value` of `ion-input` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the input and the input losing focus or from clicking the clear action within the input. +- `ionChange` is no longer emitted when the `value` of `ion-input` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the input and the input losing focus, clicking the clear action within the input, or pressing the "Enter" key. - If your application requires immediate feedback based on the user typing actively in the input, consider migrating your event listeners to using `ionInput` instead. @@ -196,7 +196,7 @@ Ionic now listens on the `keydown` event instead of the `keyup` event when deter -- `ionChange` is no longer emitted when the `value` of `ion-searchbar` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the searchbar and the searchbar losing focus. +- `ionChange` is no longer emitted when the `value` of `ion-searchbar` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the searchbar and the searchbar losing focus or pressing the "Enter" key. - If your application requires immediate feedback based on the user typing actively in the searchbar, consider migrating your event listeners to using `ionInput` instead. @@ -204,6 +204,8 @@ Ionic now listens on the `keydown` event instead of the `keyup` event when deter - The `debounce` property's default value has changed from 250 to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately. +- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`. + **Design tokens** | Token | Previous Value | New Value | diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index 59eed5f824..b21283930a 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -1019,7 +1019,7 @@ event is not necessarily fired for each alteration to an element's value. Depending on the way the users interacts with the element, the `ionChange` event fires at a different moment: - When the user commits the change explicitly (e.g. by selecting a date -from a date picker for ``, etc.). +from a date picker for ``, pressing the "Enter" key, etc.). - When the element loses focus after its value has changed: for elements where the user's interaction is typing. */ @@ -1862,21 +1862,23 @@ export class IonSearchbar { } +import type { SearchbarInputEventDetail as IIonSearchbarSearchbarInputEventDetail } from '@ionic/core'; import type { SearchbarChangeEventDetail as IIonSearchbarSearchbarChangeEventDetail } from '@ionic/core'; export declare interface IonSearchbar extends Components.IonSearchbar { /** * Emitted when the `value` of the `ion-searchbar` element has changed. */ - ionInput: EventEmitter>; + ionInput: EventEmitter>; /** * The `ionChange` event is fired for `` elements when the user modifies the element's value. Unlike the `ionInput` event, the `ionChange` event is not necessarily fired for each alteration to an element's value. -The `ionChange` event is fired when the element loses focus after its value -has been modified. This includes modifications made when clicking the clear -or cancel buttons. +The `ionChange` event is fired when the value has been committed +by the user. This can happen when the element loses focus or +when the "Enter" key is pressed. `ionChange` can also fire +when clicking the clear or cancel buttons. */ ionChange: EventEmitter>; /** diff --git a/angular/src/index.ts b/angular/src/index.ts index 7c00685f72..0059a9538b 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -114,6 +114,7 @@ export { ScrollCustomEvent, SearchbarCustomEvent, SearchbarChangeEventDetail, + SearchbarInputEventDetail, SegmentChangeEventDetail, SegmentCustomEvent, SelectChangeEventDetail, diff --git a/core/api.txt b/core/api.txt index b25334a075..07d0fbf5e5 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1162,7 +1162,7 @@ ion-searchbar,event,ionCancel,void,true ion-searchbar,event,ionChange,SearchbarChangeEventDetail,true ion-searchbar,event,ionClear,void,true ion-searchbar,event,ionFocus,void,true -ion-searchbar,event,ionInput,KeyboardEvent | null,true +ion-searchbar,event,ionInput,SearchbarInputEventDetail,true ion-searchbar,css-prop,--background ion-searchbar,css-prop,--border-radius ion-searchbar,css-prop,--box-shadow diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 3bf00a8428..28f156e818 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -32,7 +32,7 @@ import { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, Rang import { RefresherEventDetail } from "./components/refresher/refresher-interface"; import { ItemReorderEventDetail } from "./components/reorder-group/reorder-group-interface"; import { NavigationHookCallback } from "./components/route/route-interface"; -import { SearchbarChangeEventDetail } from "./components/searchbar/searchbar-interface"; +import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface"; import { SegmentChangeEventDetail } from "./components/segment/segment-interface"; import { SegmentButtonLayout } from "./components/segment-button/segment-button-interface"; import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface"; @@ -68,7 +68,7 @@ export { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, Rang export { RefresherEventDetail } from "./components/refresher/refresher-interface"; export { ItemReorderEventDetail } from "./components/reorder-group/reorder-group-interface"; export { NavigationHookCallback } from "./components/route/route-interface"; -export { SearchbarChangeEventDetail } from "./components/searchbar/searchbar-interface"; +export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface"; export { SegmentChangeEventDetail } from "./components/segment/segment-interface"; export { SegmentButtonLayout } from "./components/segment-button/segment-button-interface"; export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface"; @@ -5276,7 +5276,7 @@ declare namespace LocalJSX { */ "onIonBlur"?: (event: IonInputCustomEvent) => void; /** - * The `ionChange` event is fired for `` elements when the user modifies the element's value. Unlike the `ionInput` event, the `ionChange` event is not necessarily fired for each alteration to an element's value. Depending on the way the users interacts with the element, the `ionChange` event fires at a different moment: - When the user commits the change explicitly (e.g. by selecting a date from a date picker for ``, etc.). - When the element loses focus after its value has changed: for elements where the user's interaction is typing. + * The `ionChange` event is fired for `` elements when the user modifies the element's value. Unlike the `ionInput` event, the `ionChange` event is not necessarily fired for each alteration to an element's value. Depending on the way the users interacts with the element, the `ionChange` event fires at a different moment: - When the user commits the change explicitly (e.g. by selecting a date from a date picker for ``, pressing the "Enter" key, etc.). - When the element loses focus after its value has changed: for elements where the user's interaction is typing. */ "onIonChange"?: (event: IonInputCustomEvent) => void; /** @@ -6602,7 +6602,7 @@ declare namespace LocalJSX { */ "onIonCancel"?: (event: IonSearchbarCustomEvent) => void; /** - * The `ionChange` event is fired for `` elements when the user modifies the element's value. Unlike the `ionInput` event, the `ionChange` event is not necessarily fired for each alteration to an element's value. The `ionChange` event is fired when the element loses focus after its value has been modified. This includes modifications made when clicking the clear or cancel buttons. + * The `ionChange` event is fired for `` elements when the user modifies the element's value. Unlike the `ionInput` event, the `ionChange` event is not necessarily fired for each alteration to an element's value. The `ionChange` event is fired when the value has been committed by the user. This can happen when the element loses focus or when the "Enter" key is pressed. `ionChange` can also fire when clicking the clear or cancel buttons. */ "onIonChange"?: (event: IonSearchbarCustomEvent) => void; /** @@ -6616,7 +6616,7 @@ declare namespace LocalJSX { /** * Emitted when the `value` of the `ion-searchbar` element has changed. */ - "onIonInput"?: (event: IonSearchbarCustomEvent) => void; + "onIonInput"?: (event: IonSearchbarCustomEvent) => void; /** * Emitted when the styles change. */ diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index aa5ae57252..6d9e8b4177 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -291,10 +291,9 @@ export class Input implements ComponentInterface { * Depending on the way the users interacts with the element, the `ionChange` * event fires at a different moment: * - When the user commits the change explicitly (e.g. by selecting a date - * from a date picker for ``, etc.). + * from a date picker for ``, pressing the "Enter" key, etc.). * - When the element loses focus after its value has changed: for elements * where the user's interaction is typing. - * */ @Event() ionChange!: EventEmitter; diff --git a/core/src/components/searchbar/searchbar-interface.ts b/core/src/components/searchbar/searchbar-interface.ts index 45697096ae..a4fa573d90 100644 --- a/core/src/components/searchbar/searchbar-interface.ts +++ b/core/src/components/searchbar/searchbar-interface.ts @@ -1,5 +1,11 @@ export interface SearchbarChangeEventDetail { value?: string | null; + event?: Event; +} + +export interface SearchbarInputEventDetail { + value?: string | null; + event?: Event; } export interface SearchbarCustomEvent extends CustomEvent { diff --git a/core/src/components/searchbar/searchbar.tsx b/core/src/components/searchbar/searchbar.tsx index ab3ee5a508..f4bbcf6b03 100644 --- a/core/src/components/searchbar/searchbar.tsx +++ b/core/src/components/searchbar/searchbar.tsx @@ -9,7 +9,7 @@ import { debounceEvent, raf } from '../../utils/helpers'; import { isRTL } from '../../utils/rtl'; import { createColorClasses } from '../../utils/theme'; -import type { SearchbarChangeEventDetail } from './searchbar-interface'; +import type { SearchbarChangeEventDetail, SearchbarInputEventDetail } from './searchbar-interface'; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. @@ -26,7 +26,7 @@ export class Searchbar implements ComponentInterface { private nativeInput?: HTMLInputElement; private isCancelVisible = false; private shouldAlignLeft = true; - private originalIonInput?: EventEmitter; + private originalIonInput?: EventEmitter; /** * The value of the input when the textarea is focused. @@ -165,16 +165,17 @@ export class Searchbar implements ComponentInterface { /** * Emitted when the `value` of the `ion-searchbar` element has changed. */ - @Event() ionInput!: EventEmitter; + @Event() ionInput!: EventEmitter; /** * The `ionChange` event is fired for `` elements when the user * modifies the element's value. Unlike the `ionInput` event, the `ionChange` * event is not necessarily fired for each alteration to an element's value. * - * The `ionChange` event is fired when the element loses focus after its value - * has been modified. This includes modifications made when clicking the clear - * or cancel buttons. + * The `ionChange` event is fired when the value has been committed + * by the user. This can happen when the element loses focus or + * when the "Enter" key is pressed. `ionChange` can also fire + * when clicking the clear or cancel buttons. */ @Event() ionChange!: EventEmitter; @@ -266,13 +267,21 @@ export class Searchbar 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 }); } /** @@ -288,7 +297,7 @@ export class Searchbar implements ComponentInterface { const value = this.getValue(); if (value !== '') { this.value = ''; - this.ionInput.emit(null); + this.emitInputChange(); /** * When tapping clear button @@ -338,7 +347,7 @@ export class Searchbar implements ComponentInterface { * manually fire ionChange. */ if (value && !focused) { - this.emitValueChange(); + this.emitValueChange(ev); } if (this.nativeInput) { @@ -349,29 +358,29 @@ export class Searchbar implements ComponentInterface { /** * Update the Searchbar input value when the input changes */ - private onInput = (ev: Event) => { + private onInput = (ev: InputEvent | Event) => { const input = ev.target as HTMLInputElement | null; if (input) { this.value = input.value; } - this.ionInput.emit(ev as KeyboardEvent); + this.emitInputChange(ev); }; - private onChange = () => { - this.emitValueChange(); + private onChange = (ev: Event) => { + this.emitValueChange(ev); }; /** * Sets the Searchbar to not focused and checks if it should align left * based on whether there is a value in the searchbar or not. */ - private onBlur = () => { + private onBlur = (ev: FocusEvent) => { this.focused = false; this.ionBlur.emit(); this.positionElements(); if (this.focusedValue !== this.value) { - this.emitValueChange(); + this.emitValueChange(ev); } this.focusedValue = undefined; }; diff --git a/core/src/components/searchbar/test/events/searchbar.e2e.ts b/core/src/components/searchbar/test/events/searchbar.e2e.ts index 123443f792..1079957cbc 100644 --- a/core/src/components/searchbar/test/events/searchbar.e2e.ts +++ b/core/src/components/searchbar/test/events/searchbar.e2e.ts @@ -15,7 +15,7 @@ test.describe('searchbar: events (ionChange)', () => { await nativeInput.evaluate((e) => e.blur()); await ionChange.next(); - expect(ionChange).toHaveReceivedEventDetail({ value: 'new value' }); + expect(ionChange).toHaveReceivedEventDetail({ value: 'new value', event: { isTrusted: true } }); expect(ionChange).toHaveReceivedEventTimes(1); }); @@ -29,7 +29,7 @@ test.describe('searchbar: events (ionChange)', () => { await nativeInput.evaluate((e) => e.blur()); await ionChange.next(); - expect(ionChange).toHaveReceivedEventDetail({ value: '' }); + expect(ionChange).toHaveReceivedEventDetail({ value: '', event: { isTrusted: true } }); expect(ionChange).toHaveReceivedEventTimes(1); }); @@ -41,7 +41,7 @@ test.describe('searchbar: events (ionChange)', () => { await page.waitForChanges(); await ionChange.next(); - expect(ionChange).toHaveReceivedEventDetail({ value: '' }); + expect(ionChange).toHaveReceivedEventDetail({ value: '', event: { isTrusted: true } }); expect(ionChange).toHaveReceivedEventTimes(1); }); diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 8508fec79e..32c2cae47e 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -69,6 +69,7 @@ export { ScrollCustomEvent, SearchbarCustomEvent, SearchbarChangeEventDetail, + SearchbarInputEventDetail, SegmentChangeEventDetail, SegmentCustomEvent, SelectChangeEventDetail, diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 91c3d1c94d..69fd44e95b 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -109,6 +109,7 @@ export { ScrollCustomEvent, SearchbarCustomEvent, SearchbarChangeEventDetail, + SearchbarInputEventDetail, SegmentChangeEventDetail, SegmentCustomEvent, SelectChangeEventDetail,