mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 17:42:15 +08:00

* feat(textarea): add autoGrow - set height to scrollHeight * change 1px to inherit, remove additional 4px
346 lines
8.2 KiB
TypeScript
346 lines
8.2 KiB
TypeScript
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
|
|
|
import { Color, Mode, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
|
|
import { debounceEvent, findItemLabel } from '../../utils/helpers';
|
|
import { createColorClasses } from '../../utils/theme';
|
|
|
|
@Component({
|
|
tag: 'ion-textarea',
|
|
styleUrls: {
|
|
ios: 'textarea.ios.scss',
|
|
md: 'textarea.md.scss'
|
|
},
|
|
scoped: true
|
|
})
|
|
export class Textarea implements ComponentInterface {
|
|
|
|
private nativeInput?: HTMLTextAreaElement;
|
|
private inputId = `ion-input-${textareaIds++}`;
|
|
private didBlurAfterEdit = false;
|
|
|
|
@Element() el!: HTMLElement;
|
|
|
|
@State() hasFocus = false;
|
|
|
|
/**
|
|
* The mode determines which platform styles to use.
|
|
*/
|
|
@Prop() mode!: Mode;
|
|
|
|
/**
|
|
* The color to use from your application's color palette.
|
|
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
|
* For more information on colors, see [theming](/docs/theming/basics).
|
|
*/
|
|
@Prop() color?: Color;
|
|
|
|
/**
|
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
*/
|
|
@Prop() autocapitalize = 'none';
|
|
|
|
/**
|
|
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
|
|
*/
|
|
@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.
|
|
*/
|
|
@Prop({ mutable: true }) clearOnEdit = false;
|
|
|
|
/**
|
|
* Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke.
|
|
*/
|
|
@Prop() debounce = 0;
|
|
|
|
@Watch('debounce')
|
|
protected debounceChanged() {
|
|
this.ionChange = debounceEvent(this.ionChange, this.debounce);
|
|
}
|
|
|
|
/**
|
|
* If `true`, the user cannot interact with the textarea.
|
|
*/
|
|
@Prop() disabled = false;
|
|
|
|
@Watch('disabled')
|
|
protected disabledChanged() {
|
|
this.emitStyle();
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
@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.
|
|
*/
|
|
@Prop() minlength?: number;
|
|
|
|
/**
|
|
* The name of the control, which is submitted with the form data.
|
|
*/
|
|
@Prop() name: string = this.inputId;
|
|
|
|
/**
|
|
* Instructional text that shows before the input has a value.
|
|
*/
|
|
@Prop() placeholder?: string | null;
|
|
|
|
/**
|
|
* If `true`, the user cannot modify the value.
|
|
*/
|
|
@Prop() readonly = false;
|
|
|
|
/**
|
|
* If `true`, the user must fill in a value before submitting a form.
|
|
*/
|
|
@Prop() required = false;
|
|
|
|
/**
|
|
* If `true`, the element will have its spelling and grammar checked.
|
|
*/
|
|
@Prop() spellcheck = false;
|
|
|
|
/**
|
|
* The visible width of the text control, in average character widths. If it is specified, it must be a positive integer.
|
|
*/
|
|
@Prop() cols?: number;
|
|
|
|
/**
|
|
* The number of visible text lines for the control.
|
|
*/
|
|
@Prop() rows?: number;
|
|
|
|
/**
|
|
* Indicates how the control wraps text.
|
|
*/
|
|
@Prop() wrap?: 'hard' | 'soft' | 'off';
|
|
|
|
/**
|
|
* If `true`, the element height will increase based on the value.
|
|
*/
|
|
@Prop() autoGrow = false;
|
|
|
|
/**
|
|
* The value of the textarea.
|
|
*/
|
|
@Prop({ mutable: true }) value?: string | null = '';
|
|
|
|
/**
|
|
* Update the native input element when the value changes
|
|
*/
|
|
@Watch('value')
|
|
protected valueChanged() {
|
|
const nativeInput = this.nativeInput;
|
|
const value = this.getValue();
|
|
if (nativeInput && nativeInput.value !== value) {
|
|
nativeInput.value = value;
|
|
}
|
|
this.runAutoGrow();
|
|
this.emitStyle();
|
|
this.ionChange.emit({ value });
|
|
}
|
|
|
|
/**
|
|
* Emitted when the input value has changed.
|
|
*/
|
|
@Event() ionChange!: EventEmitter<TextareaChangeEventDetail>;
|
|
|
|
/**
|
|
* Emitted when a keyboard input ocurred.
|
|
*/
|
|
@Event() ionInput!: EventEmitter<KeyboardEvent>;
|
|
|
|
/**
|
|
* Emitted when the styles change.
|
|
* @internal
|
|
*/
|
|
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
|
|
|
/**
|
|
* Emitted when the input loses focus.
|
|
*/
|
|
@Event() ionBlur!: EventEmitter<void>;
|
|
|
|
/**
|
|
* Emitted when the input has focus.
|
|
*/
|
|
@Event() ionFocus!: EventEmitter<void>;
|
|
|
|
/**
|
|
* Emitted when the input has been created.
|
|
* @internal
|
|
*/
|
|
@Event() ionInputDidLoad!: EventEmitter<void>;
|
|
|
|
/**
|
|
* Emitted when the input has been removed.
|
|
* @internal
|
|
*/
|
|
@Event() ionInputDidUnload!: EventEmitter<void>;
|
|
|
|
componentWillLoad() {
|
|
this.emitStyle();
|
|
}
|
|
|
|
componentDidLoad() {
|
|
this.debounceChanged();
|
|
|
|
this.runAutoGrow();
|
|
|
|
this.ionInputDidLoad.emit();
|
|
}
|
|
|
|
private runAutoGrow() {
|
|
if (this.nativeInput && this.autoGrow) {
|
|
this.nativeInput.style.height = 'inherit';
|
|
this.nativeInput.style.height = this.nativeInput.scrollHeight + 'px';
|
|
}
|
|
}
|
|
|
|
componentDidUnload() {
|
|
this.ionInputDidUnload.emit();
|
|
}
|
|
|
|
/**
|
|
* Sets focus on the specified `ion-textarea`. Use this method instead of the global
|
|
* `input.focus()`.
|
|
*/
|
|
@Method()
|
|
setFocus() {
|
|
if (this.nativeInput) {
|
|
this.nativeInput.focus();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the native `<textarea>` element used under the hood.
|
|
*/
|
|
@Method()
|
|
getInputElement(): Promise<HTMLTextAreaElement> {
|
|
return Promise.resolve(this.nativeInput!);
|
|
}
|
|
|
|
private emitStyle() {
|
|
this.ionStyle.emit({
|
|
'interactive': true,
|
|
'textarea': true,
|
|
'input': true,
|
|
'interactive-disabled': this.disabled,
|
|
'has-placeholder': this.placeholder != null,
|
|
'has-value': this.hasValue(),
|
|
'has-focus': this.hasFocus
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if we need to clear the text input if clearOnEdit is enabled
|
|
*/
|
|
private checkClearOnEdit() {
|
|
if (!this.clearOnEdit) {
|
|
return;
|
|
}
|
|
|
|
// Did the input value change after it was blurred and edited?
|
|
if (this.didBlurAfterEdit && this.hasValue()) {
|
|
// Clear the input
|
|
this.value = '';
|
|
}
|
|
|
|
// Reset the flag
|
|
this.didBlurAfterEdit = false;
|
|
}
|
|
|
|
private focusChange() {
|
|
// If clearOnEdit is enabled and the input blurred but has a value, set a flag
|
|
if (this.clearOnEdit && !this.hasFocus && this.hasValue()) {
|
|
this.didBlurAfterEdit = true;
|
|
}
|
|
this.emitStyle();
|
|
}
|
|
|
|
private hasValue(): boolean {
|
|
return this.getValue() !== '';
|
|
}
|
|
|
|
private getValue(): string {
|
|
return this.value || '';
|
|
}
|
|
|
|
private onInput = (ev: Event) => {
|
|
if (this.nativeInput) {
|
|
this.value = this.nativeInput.value;
|
|
}
|
|
this.emitStyle();
|
|
this.ionInput.emit(ev as KeyboardEvent);
|
|
}
|
|
|
|
private onFocus = () => {
|
|
this.hasFocus = true;
|
|
this.focusChange();
|
|
|
|
this.ionFocus.emit();
|
|
}
|
|
|
|
private onBlur = () => {
|
|
this.hasFocus = false;
|
|
this.focusChange();
|
|
|
|
this.ionBlur.emit();
|
|
}
|
|
|
|
private onKeyDown = () => {
|
|
this.checkClearOnEdit();
|
|
}
|
|
|
|
hostData() {
|
|
return {
|
|
'aria-disabled': this.disabled ? 'true' : null,
|
|
class: {
|
|
...createColorClasses(this.color),
|
|
[`${this.mode}`]: true,
|
|
}
|
|
};
|
|
}
|
|
|
|
render() {
|
|
const value = this.getValue();
|
|
const labelId = this.inputId + '-lbl';
|
|
const label = findItemLabel(this.el);
|
|
if (label) {
|
|
label.id = labelId;
|
|
}
|
|
|
|
return (
|
|
<textarea
|
|
class="native-textarea"
|
|
ref={el => this.nativeInput = el}
|
|
autoCapitalize={this.autocapitalize}
|
|
autoFocus={this.autofocus}
|
|
disabled={this.disabled}
|
|
maxLength={this.maxlength}
|
|
minLength={this.minlength}
|
|
name={this.name}
|
|
placeholder={this.placeholder || ''}
|
|
readOnly={this.readonly}
|
|
required={this.required}
|
|
spellCheck={this.spellcheck}
|
|
cols={this.cols}
|
|
rows={this.rows}
|
|
wrap={this.wrap}
|
|
onInput={this.onInput}
|
|
onBlur={this.onBlur}
|
|
onFocus={this.onFocus}
|
|
onKeyDown={this.onKeyDown}
|
|
>
|
|
{value}
|
|
</textarea>
|
|
);
|
|
}
|
|
}
|
|
|
|
let textareaIds = 0;
|