mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 01:52:19 +08:00
Merge branch 'feature-7.0' into 7-sync-09-23-22
This commit is contained in:
12
BREAKING.md
12
BREAKING.md
@ -21,6 +21,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
|
||||
- [Range](#version-7x-range)
|
||||
- [Segment](#version-7x-segment)
|
||||
- [Slides](#version-7x-slides)
|
||||
- [Textarea](#version-7x-textarea)
|
||||
- [Virtual Scroll](#version-7x-virtual-scroll)
|
||||
- [Utilities](#version-7x-utilities)
|
||||
- [hidden attribute](#version-7x-hidden-attribute)
|
||||
@ -109,6 +110,17 @@ Developers using these components will need to migrate to using Swiper.js direct
|
||||
- [React](https://ionicframework.com/docs/react/slides)
|
||||
- [Vue](https://ionicframework.com/docs/vue/slides)
|
||||
|
||||
<h4 id="version-7x-textarea">Textarea</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-textarea` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the textarea and the textarea losing focus.
|
||||
|
||||
- If your application requires immediate feedback based on the user typing actively in the textarea, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- 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"`.
|
||||
|
||||
|
||||
<h4 id="version-7x-virtual-scroll">Virtual Scroll</h4>
|
||||
|
||||
`ion-virtual-scroll` has been removed from Ionic.
|
||||
|
@ -5,7 +5,7 @@ import { ValueAccessor } from './value-accessor';
|
||||
|
||||
@Directive({
|
||||
/* tslint:disable-next-line:directive-selector */
|
||||
selector: 'ion-textarea,ion-searchbar',
|
||||
selector: 'ion-searchbar',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
@ -26,7 +26,7 @@ export class TextValueAccessorDirective extends ValueAccessor {
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-input:not([type=number])',
|
||||
selector: 'ion-input:not([type=number]),ion-textarea',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
@ -35,6 +35,7 @@ export class TextValueAccessorDirective extends ValueAccessor {
|
||||
},
|
||||
],
|
||||
})
|
||||
// TODO rename this value accessor to `TextValueAccessorDirective` when search-bar is updated
|
||||
export class InputValueAccessorDirective extends ValueAccessor {
|
||||
constructor(injector: Injector, el: ElementRef) {
|
||||
super(injector, el);
|
||||
|
@ -1816,13 +1816,19 @@ export class IonText {
|
||||
import type { TextareaChangeEventDetail as ITextareaTextareaChangeEventDetail } from '@ionic/core';
|
||||
export declare interface IonTextarea extends Components.IonTextarea {
|
||||
/**
|
||||
* Emitted when the input value has changed.
|
||||
* The `ionChange` event is fired for `<ion-textarea>` 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.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<ITextareaTextareaChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when a keyboard input occurred.
|
||||
* Ths `ionInput` event fires when the `value` of an `<ion-textarea>` element
|
||||
has been changed.
|
||||
*/
|
||||
ionInput: EventEmitter<CustomEvent<InputEvent>>;
|
||||
ionInput: EventEmitter<CustomEvent<InputEvent | null>>;
|
||||
/**
|
||||
* Emitted when the input loses focus.
|
||||
*/
|
||||
|
18
angular/test/base/e2e/src/textarea.spec.ts
Normal file
18
angular/test/base/e2e/src/textarea.spec.ts
Normal file
@ -0,0 +1,18 @@
|
||||
describe('Textarea', () => {
|
||||
beforeEach(() => cy.visit('/textarea'));
|
||||
|
||||
it('should become valid', () => {
|
||||
cy.get('#status').should('have.text', 'INVALID');
|
||||
|
||||
cy.get('ion-textarea').type('hello');
|
||||
|
||||
cy.get('#status').should('have.text', 'VALID');
|
||||
});
|
||||
|
||||
it('should update the form control value when typing', () => {
|
||||
cy.get('#value').contains(`"textarea": ""`);
|
||||
cy.get('ion-textarea').type('hello');
|
||||
|
||||
cy.get('#value').contains(`"textarea": "hello"`);
|
||||
});
|
||||
});
|
@ -25,6 +25,7 @@ const routes: Routes = [
|
||||
{ path: 'accordions', component: AccordionComponent },
|
||||
{ path: 'alerts', component: AlertComponent },
|
||||
{ path: 'inputs', component: InputsComponent },
|
||||
{ path: 'textarea', loadChildren: () => import('./textarea/textarea.module').then(m => m.TextareaModule) },
|
||||
{ path: 'form', component: FormComponent },
|
||||
{ path: 'modals', component: ModalComponent },
|
||||
{ path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) },
|
||||
|
@ -61,7 +61,7 @@
|
||||
Form Status: <span id="status">{{ profileForm.status }}</span>
|
||||
</p>
|
||||
<p>
|
||||
Form Status: <span id="data">{{ profileForm.value | json }}</span>
|
||||
Form value: <span id="data">{{ profileForm.value | json }}</span>
|
||||
</p>
|
||||
<p>
|
||||
Form Submit: <span id="submit">{{submitted}}</span>
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { TextareaComponent } from "./textarea.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: TextareaComponent
|
||||
}
|
||||
])
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class TextareaRoutingModule { }
|
16
angular/test/base/src/app/textarea/textarea.component.html
Normal file
16
angular/test/base/src/app/textarea/textarea.component.html
Normal file
@ -0,0 +1,16 @@
|
||||
<ion-content>
|
||||
<form [formGroup]="form">
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>Textarea</ion-label>
|
||||
<ion-textarea formControlName="textarea"></ion-textarea>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</form>
|
||||
<p>
|
||||
Form status: <span id="status">{{ form.status }}</span>
|
||||
</p>
|
||||
<p>
|
||||
Form value: <span id="value">{{ form.value | json }}</span>
|
||||
</p>
|
||||
</ion-content>
|
17
angular/test/base/src/app/textarea/textarea.component.ts
Normal file
17
angular/test/base/src/app/textarea/textarea.component.ts
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-textarea',
|
||||
templateUrl: 'textarea.component.html',
|
||||
})
|
||||
export class TextareaComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
textarea: ['', Validators.required]
|
||||
})
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
21
angular/test/base/src/app/textarea/textarea.module.ts
Normal file
21
angular/test/base/src/app/textarea/textarea.module.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { IonicModule } from "@ionic/angular";
|
||||
|
||||
import { TextareaRoutingModule } from "./textarea-routing.module";
|
||||
import { TextareaComponent } from "./textarea.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
IonicModule,
|
||||
TextareaRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
TextareaComponent
|
||||
]
|
||||
})
|
||||
export class TextareaModule { }
|
@ -1307,7 +1307,7 @@ ion-textarea,method,setFocus,setFocus() => Promise<void>
|
||||
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,InputEvent | null,true
|
||||
ion-textarea,css-prop,--background
|
||||
ion-textarea,css-prop,--border-radius
|
||||
ion-textarea,css-prop,--color
|
||||
|
22
core/src/components.d.ts
vendored
22
core/src/components.d.ts
vendored
@ -2719,7 +2719,7 @@ export namespace Components {
|
||||
*/
|
||||
"autofocus": boolean;
|
||||
/**
|
||||
* If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types.
|
||||
* If `true`, the value will be cleared after focus upon edit.
|
||||
*/
|
||||
"clearOnEdit": boolean;
|
||||
/**
|
||||
@ -2731,7 +2731,7 @@ export namespace Components {
|
||||
*/
|
||||
"cols"?: number;
|
||||
/**
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. This also impacts form bindings such as `ngModel` or `v-model`.
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke.
|
||||
*/
|
||||
"debounce": number;
|
||||
/**
|
||||
@ -2751,11 +2751,11 @@ export namespace Components {
|
||||
*/
|
||||
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
|
||||
/**
|
||||
* If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, this attribute specifies the maximum number of characters that the user can enter.
|
||||
* This attribute specifies the maximum number of characters that the user can enter.
|
||||
*/
|
||||
"maxlength"?: number;
|
||||
/**
|
||||
* If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, this attribute specifies the minimum number of characters that the user can enter.
|
||||
* This attribute specifies the minimum number of characters that the user can enter.
|
||||
*/
|
||||
"minlength"?: number;
|
||||
/**
|
||||
@ -6518,7 +6518,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"autofocus"?: boolean;
|
||||
/**
|
||||
* If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types.
|
||||
* If `true`, the value will be cleared after focus upon edit.
|
||||
*/
|
||||
"clearOnEdit"?: boolean;
|
||||
/**
|
||||
@ -6530,7 +6530,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"cols"?: number;
|
||||
/**
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. This also impacts form bindings such as `ngModel` or `v-model`.
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke.
|
||||
*/
|
||||
"debounce"?: number;
|
||||
/**
|
||||
@ -6546,11 +6546,11 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
|
||||
/**
|
||||
* If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, this attribute specifies the maximum number of characters that the user can enter.
|
||||
* This attribute specifies the maximum number of characters that the user can enter.
|
||||
*/
|
||||
"maxlength"?: number;
|
||||
/**
|
||||
* If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, this attribute specifies the minimum number of characters that the user can enter.
|
||||
* This attribute specifies the minimum number of characters that the user can enter.
|
||||
*/
|
||||
"minlength"?: number;
|
||||
/**
|
||||
@ -6566,7 +6566,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"onIonBlur"?: (event: IonTextareaCustomEvent<FocusEvent>) => void;
|
||||
/**
|
||||
* Emitted when the input value has changed.
|
||||
* The `ionChange` event is fired for `<ion-textarea>` 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.
|
||||
*/
|
||||
"onIonChange"?: (event: IonTextareaCustomEvent<TextareaChangeEventDetail>) => void;
|
||||
/**
|
||||
@ -6574,9 +6574,9 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"onIonFocus"?: (event: IonTextareaCustomEvent<FocusEvent>) => void;
|
||||
/**
|
||||
* Emitted when a keyboard input occurred.
|
||||
* Ths `ionInput` event fires when the `value` of an `<ion-textarea>` element has been changed.
|
||||
*/
|
||||
"onIonInput"?: (event: IonTextareaCustomEvent<InputEvent>) => void;
|
||||
"onIonInput"?: (event: IonTextareaCustomEvent<InputEvent | null>) => void;
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
*/
|
||||
|
@ -344,6 +344,7 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
private emitValueChange() {
|
||||
const { value } = this;
|
||||
// Checks for both null and undefined values
|
||||
const newValue = value == null ? value : value.toString();
|
||||
this.ionChange.emit({ value: newValue });
|
||||
}
|
||||
@ -368,7 +369,7 @@ export class Input implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
private onInput = (ev: Event) => {
|
||||
private onInput = (ev: InputEvent | Event) => {
|
||||
const input = ev.target as HTMLInputElement | null;
|
||||
if (input) {
|
||||
this.value = input.value || '';
|
||||
|
93
core/src/components/textarea/test/textarea-events.e2e.ts
Normal file
93
core/src/components/textarea/test/textarea-events.e2e.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('textarea: events: ionChange', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
|
||||
test.describe('when the textarea is blurred', () => {
|
||||
test('should emit if the value has changed', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea></ion-textarea>`);
|
||||
|
||||
const nativeTextarea = page.locator('ion-textarea textarea');
|
||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
await nativeTextarea.type('new value', { delay: 100 });
|
||||
// Value change is not emitted until the control is blurred.
|
||||
await nativeTextarea.evaluate((e) => e.blur());
|
||||
|
||||
await ionChangeSpy.next();
|
||||
|
||||
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value' });
|
||||
expect(ionChangeSpy).toHaveReceivedEventTimes(1);
|
||||
});
|
||||
|
||||
test('should emit if the textarea is cleared with an initial value', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea clear-on-edit="true" value="123"></ion-textarea>`);
|
||||
|
||||
const textarea = page.locator('ion-textarea');
|
||||
const nativeTextarea = textarea.locator('textarea');
|
||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
await nativeTextarea.type('new value');
|
||||
|
||||
await nativeTextarea.evaluate((e) => e.blur());
|
||||
|
||||
await ionChangeSpy.next();
|
||||
|
||||
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: 'new value' });
|
||||
expect(ionChangeSpy).toHaveReceivedEventTimes(1);
|
||||
});
|
||||
|
||||
test('should not emit if the value is set programmatically', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea></ion-textarea>`);
|
||||
|
||||
const textarea = page.locator('ion-textarea');
|
||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
await textarea.evaluate((el: HTMLIonTextareaElement) => {
|
||||
el.value = 'new value';
|
||||
});
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(ionChangeSpy).toHaveReceivedEventTimes(0);
|
||||
|
||||
// Update the value again to make sure it doesn't emit a second time
|
||||
await textarea.evaluate((el: HTMLIonTextareaElement) => {
|
||||
el.value = 'new value 2';
|
||||
});
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(ionChangeSpy).toHaveReceivedEventTimes(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('textarea: events: ionInput', () => {
|
||||
test('should emit when the user types', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea value="some value"></ion-textarea>`);
|
||||
|
||||
const ionInputSpy = await page.spyOnEvent('ionInput');
|
||||
|
||||
const nativeTextarea = page.locator('ion-textarea textarea');
|
||||
await nativeTextarea.type('new value', { delay: 100 });
|
||||
|
||||
expect(ionInputSpy).toHaveReceivedEventDetail({ isTrusted: true });
|
||||
});
|
||||
|
||||
test('should emit when the textarea is cleared on edit', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea clear-on-edit="true" value="some value"></ion-textarea>`);
|
||||
|
||||
const ionInputSpy = await page.spyOnEvent('ionInput');
|
||||
const textarea = page.locator('ion-textarea');
|
||||
|
||||
await textarea.click();
|
||||
await textarea.press('Backspace');
|
||||
|
||||
expect(ionInputSpy).toHaveReceivedEventTimes(1);
|
||||
expect(ionInputSpy).toHaveReceivedEventDetail(null);
|
||||
});
|
||||
});
|
@ -21,9 +21,13 @@ import { createColorClasses } from '../../utils/theme';
|
||||
export class Textarea implements ComponentInterface {
|
||||
private nativeInput?: HTMLTextAreaElement;
|
||||
private inputId = `ion-textarea-${textareaIds++}`;
|
||||
private didBlurAfterEdit = false;
|
||||
private didBlurAfterEdit = this.hasValue();
|
||||
private textareaWrapper?: HTMLElement;
|
||||
private inheritedAttributes: Attributes = {};
|
||||
/**
|
||||
* The value of the input when the textarea is focused.
|
||||
*/
|
||||
private focusedValue?: string | null;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@ -48,18 +52,18 @@ export class Textarea implements ComponentInterface {
|
||||
@Prop() autofocus = false;
|
||||
|
||||
/**
|
||||
* If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types.
|
||||
* If `true`, the value will be cleared after focus upon edit.
|
||||
*/
|
||||
@Prop({ mutable: true }) clearOnEdit = false;
|
||||
@Prop() clearOnEdit = false;
|
||||
|
||||
/**
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. This also impacts form bindings such as `ngModel` or `v-model`.
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke.
|
||||
*/
|
||||
@Prop() debounce = 0;
|
||||
|
||||
@Watch('debounce')
|
||||
protected debounceChanged() {
|
||||
this.ionChange = debounceEvent(this.ionChange, this.debounce);
|
||||
this.ionInput = debounceEvent(this.ionInput, this.debounce);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,12 +91,12 @@ export class Textarea implements ComponentInterface {
|
||||
@Prop() enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';
|
||||
|
||||
/**
|
||||
* If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, this attribute specifies the maximum number of characters that the user can enter.
|
||||
* This attribute specifies the maximum number of characters that the user can enter.
|
||||
*/
|
||||
@Prop() maxlength?: number;
|
||||
|
||||
/**
|
||||
* If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, this attribute specifies the minimum number of characters that the user can enter.
|
||||
* This attribute specifies the minimum number of characters that the user can enter.
|
||||
*/
|
||||
@Prop() minlength?: number;
|
||||
|
||||
@ -159,18 +163,23 @@ export class Textarea implements ComponentInterface {
|
||||
}
|
||||
this.runAutoGrow();
|
||||
this.emitStyle();
|
||||
this.ionChange.emit({ value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted when the input value has changed.
|
||||
* The `ionChange` event is fired for `<ion-textarea>` 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.
|
||||
*/
|
||||
@Event() ionChange!: EventEmitter<TextareaChangeEventDetail>;
|
||||
|
||||
/**
|
||||
* Emitted when a keyboard input occurred.
|
||||
* Ths `ionInput` event fires when the `value` of an `<ion-textarea>` element
|
||||
* has been changed.
|
||||
*/
|
||||
@Event() ionInput!: EventEmitter<InputEvent>;
|
||||
@Event() ionInput!: EventEmitter<InputEvent | null>;
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
@ -252,6 +261,21 @@ export class Textarea implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an `ionChange` event.
|
||||
*
|
||||
* This API should be called for user committed changes.
|
||||
* This API should not be used for external value changes.
|
||||
*/
|
||||
private emitValueChange() {
|
||||
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 });
|
||||
}
|
||||
|
||||
private runAutoGrow() {
|
||||
if (this.nativeInput && this.autoGrow) {
|
||||
writeTask(() => {
|
||||
@ -278,6 +302,8 @@ export class Textarea implements ComponentInterface {
|
||||
this.value = '';
|
||||
}
|
||||
|
||||
this.ionInput.emit(null);
|
||||
|
||||
// Reset the flag
|
||||
this.didBlurAfterEdit = false;
|
||||
}
|
||||
@ -298,16 +324,26 @@ export class Textarea implements ComponentInterface {
|
||||
return this.value || '';
|
||||
}
|
||||
|
||||
// `Event` type is used instead of `InputEvent`
|
||||
// since the types from Stencil are not derived
|
||||
// from the element (e.g. textarea and input
|
||||
// should be InputEvent, but all other elements
|
||||
// should be Event).
|
||||
private onInput = (ev: Event) => {
|
||||
if (this.nativeInput) {
|
||||
this.value = this.nativeInput.value;
|
||||
const input = ev.target as HTMLTextAreaElement | null;
|
||||
if (input) {
|
||||
this.value = input.value || '';
|
||||
}
|
||||
this.emitStyle();
|
||||
this.ionInput.emit(ev as InputEvent);
|
||||
};
|
||||
|
||||
private onChange = () => {
|
||||
this.emitValueChange();
|
||||
};
|
||||
|
||||
private onFocus = (ev: FocusEvent) => {
|
||||
this.hasFocus = true;
|
||||
this.focusedValue = this.value;
|
||||
this.focusChange();
|
||||
|
||||
this.ionFocus.emit(ev);
|
||||
@ -317,6 +353,14 @@ export class Textarea implements ComponentInterface {
|
||||
this.hasFocus = false;
|
||||
this.focusChange();
|
||||
|
||||
if (this.focusedValue !== this.value) {
|
||||
/**
|
||||
* Emits the `ionChange` event when the textarea value
|
||||
* is different than the value when the textarea was focused.
|
||||
*/
|
||||
this.emitValueChange();
|
||||
}
|
||||
|
||||
this.ionBlur.emit(ev);
|
||||
};
|
||||
|
||||
@ -361,6 +405,7 @@ export class Textarea implements ComponentInterface {
|
||||
rows={this.rows}
|
||||
wrap={this.wrap}
|
||||
onInput={this.onInput}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
onFocus={this.onFocus}
|
||||
onKeyDown={this.onKeyDown}
|
||||
|
Reference in New Issue
Block a user