fix(textarea): textarea with autogrow will size to its contents (#24205)
Resolves #24793, #21242
4
core/src/components.d.ts
vendored
@ -2803,7 +2803,7 @@ export namespace Components {
|
||||
}
|
||||
interface IonTextarea {
|
||||
/**
|
||||
* If `true`, the element height will increase based on the value.
|
||||
* If `true`, the textarea container will grow and shrink based on the contents of the textarea.
|
||||
*/
|
||||
"autoGrow": boolean;
|
||||
/**
|
||||
@ -6786,7 +6786,7 @@ declare namespace LocalJSX {
|
||||
}
|
||||
interface IonTextarea {
|
||||
/**
|
||||
* If `true`, the element height will increase based on the value.
|
||||
* If `true`, the textarea container will grow and shrink based on the contents of the textarea.
|
||||
*/
|
||||
"autoGrow"?: boolean;
|
||||
/**
|
||||
|
@ -2,11 +2,9 @@ import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('textarea: autogrow', () => {
|
||||
test.skip('should not have visual regressions', async ({ page }) => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto(`/src/components/textarea/test/autogrow`);
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
await page.setIonViewport();
|
||||
|
||||
expect(await page.screenshot()).toMatchSnapshot(`textarea-autogrow-diff-${page.getSnapshotSettings()}.png`);
|
||||
|
After Width: | Height: | Size: 411 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 298 KiB |
After Width: | Height: | Size: 411 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 298 KiB |
After Width: | Height: | Size: 374 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 252 KiB |
After Width: | Height: | Size: 375 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 252 KiB |
@ -67,12 +67,6 @@
|
||||
<ion-label color="primary">Clear on Edit</ion-label>
|
||||
<ion-textarea clear-on-edit="true"></ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
<!-- TODO: Re-add auto grow with PR#24205 -->
|
||||
<!-- <ion-item>
|
||||
<ion-label color="primary">Autogrow</ion-label>
|
||||
<ion-textarea auto-grow="true"></ion-textarea>
|
||||
</ion-item> -->
|
||||
</ion-list>
|
||||
|
||||
<div class="ion-text-center">
|
||||
|
@ -26,7 +26,7 @@
|
||||
--placeholder-color: initial;
|
||||
--placeholder-font-style: initial;
|
||||
--placeholder-font-weight: initial;
|
||||
--placeholder-opacity: .5;
|
||||
--placeholder-opacity: 0.5;
|
||||
--padding-top: 0;
|
||||
--padding-end: 0;
|
||||
--padding-bottom: 0;
|
||||
@ -71,22 +71,41 @@
|
||||
--padding-start: 0;
|
||||
}
|
||||
|
||||
|
||||
// Native Textarea
|
||||
// --------------------------------------------------
|
||||
|
||||
.textarea-wrapper {
|
||||
display: grid;
|
||||
|
||||
min-width: inherit;
|
||||
max-width: inherit;
|
||||
min-height: inherit;
|
||||
max-height: inherit;
|
||||
|
||||
&::after {
|
||||
// This technique is used for an auto-resizing textarea.
|
||||
// The text contents are reflected as a pseudo-element that is visually hidden.
|
||||
// This causes the textarea container to grow as needed to fit the contents.
|
||||
|
||||
white-space: pre-wrap;
|
||||
|
||||
content: attr(data-replicated-value) " ";
|
||||
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.native-textarea,
|
||||
.textarea-wrapper::after {
|
||||
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
|
||||
@include text-inherit();
|
||||
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
}
|
||||
|
||||
.native-textarea {
|
||||
@include border-radius(var(--border-radius));
|
||||
@include margin(0);
|
||||
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
|
||||
@include text-inherit();
|
||||
|
||||
display: block;
|
||||
|
||||
@ -103,6 +122,8 @@
|
||||
resize: none;
|
||||
appearance: none;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
&::placeholder {
|
||||
@include padding(0);
|
||||
|
||||
@ -117,7 +138,7 @@
|
||||
}
|
||||
|
||||
.native-textarea[disabled] {
|
||||
opacity: .4;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
// Input Cover: Unfocused
|
||||
@ -136,6 +157,15 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host([auto-grow]) .cloned-input {
|
||||
// Workaround for webkit rendering issue with scroll assist.
|
||||
// When cloning the textarea and scrolling into view,
|
||||
// a white box is rendered from the difference in height
|
||||
// from the auto grow container.
|
||||
// This change forces the cloned input to match the true
|
||||
// height of the textarea container.
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// Item Floating: Placeholder
|
||||
// ----------------------------------------------------------------
|
||||
|
@ -1,10 +1,10 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h, readTask } 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 { Attributes } from '../../utils/helpers';
|
||||
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers';
|
||||
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@ -147,9 +147,10 @@ export class Textarea implements ComponentInterface {
|
||||
@Prop() wrap?: 'hard' | 'soft' | 'off';
|
||||
|
||||
/**
|
||||
* If `true`, the element height will increase based on the value.
|
||||
* If `true`, the textarea container will grow and shrink based
|
||||
* on the contents of the textarea.
|
||||
*/
|
||||
@Prop() autoGrow = false;
|
||||
@Prop({ reflect: true }) autoGrow = false;
|
||||
|
||||
/**
|
||||
* The value of the textarea.
|
||||
@ -227,20 +228,7 @@ export class Textarea implements ComponentInterface {
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
raf(() => this.runAutoGrow());
|
||||
}
|
||||
|
||||
private runAutoGrow() {
|
||||
const nativeInput = this.nativeInput;
|
||||
if (nativeInput && this.autoGrow) {
|
||||
readTask(() => {
|
||||
nativeInput.style.height = 'auto';
|
||||
nativeInput.style.height = nativeInput.scrollHeight + 'px';
|
||||
if (this.textareaWrapper) {
|
||||
this.textareaWrapper.style.height = nativeInput.scrollHeight + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
this.runAutoGrow();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -286,6 +274,18 @@ export class Textarea implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
private runAutoGrow() {
|
||||
if (this.nativeInput && this.autoGrow) {
|
||||
writeTask(() => {
|
||||
if (this.textareaWrapper) {
|
||||
// Replicated value is an attribute to be used in the stylesheet
|
||||
// to set the inner contents of a pseudo element.
|
||||
this.textareaWrapper.dataset.replicatedValue = this.value ?? '';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need to clear the text input if clearOnEdit is enabled
|
||||
*/
|
||||
|