fix(textarea): textarea with autogrow will size to its contents (#24205)

Resolves #24793, #21242
This commit is contained in:
Sean Perkins
2022-07-27 11:36:48 -04:00
committed by GitHub
parent 0390509919
commit a9cf2ab870
17 changed files with 56 additions and 34 deletions

View File

@ -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;
/**

View File

@ -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`);

View File

@ -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">

View File

@ -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
// ----------------------------------------------------------------

View File

@ -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
*/