fix(textarea): add intersection observer for textarea recalculate

This commit is contained in:
jorenbroekema 2020-10-15 13:06:50 +02:00
parent 2dc85b14d3
commit c3a581e281
4 changed files with 55 additions and 1 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/textarea': patch
---
Added Intersection Observer to ensure textarea gets recalculated on visibility changes, e.g. when it is inside a dialog, tabs, or accordion when they are hidden initially.

View file

@ -29,6 +29,7 @@ export const main = () => html`
- `max-rows` attribute to set the amount of rows it should resize to, before it will scroll - `max-rows` attribute to set the amount of rows it should resize to, before it will scroll
- `rows` attribute to set the minimum amount of rows - `rows` attribute to set the minimum amount of rows
- `readonly` attribute to prevent changing the content - `readonly` attribute to prevent changing the content
- Uses Intersection Observer for detecting visibility change, making sure it resizes
## How to use ## How to use
@ -120,3 +121,33 @@ export const validation = () => {
`; `;
}; };
``` ```
### Intersection Observer
It could be that your textarea is inside a hidden container, for example for a dialog or accordion or tabs.
When it is hidden, the resizing is calculated based on the visible space of the text.
Therefore, an Intersection Observer observes visibility changes of the textarea relative to the viewport, and resizes the textarea when a visibility change happens.
> For old browsers like old Edge or IE11, a [polyfill](https://github.com/w3c/IntersectionObserver/tree/master/polyfill) is required to be added on the application level for this to work.
> For most cases, the optimized default will suffice.
In the demo below you can see that the textarea is correctly calculated to 4 maximum rows, whereas without the observer, it would be on 2 rows and only resize on user input.
```js preview-story
export const hidden = () => html`
<div style="display: none">
<lion-textarea
.modelValue="${'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}"
label="Stops growing after 4 rows"
max-rows="4"
></lion-textarea>
</div>
<button
@click=${e =>
(e.target.previousElementSibling.style.display =
e.target.previousElementSibling.style.display === 'block' ? 'none' : 'block')}
>
Toggle display
</button>
`;
```

View file

@ -73,6 +73,8 @@ export class LionTextarea extends NativeTextFieldMixin(LionFieldWithTextArea) {
// eslint-disable-next-line wc/guard-super-call // eslint-disable-next-line wc/guard-super-call
super.connectedCallback(); super.connectedCallback();
this.__initializeAutoresize(); this.__initializeAutoresize();
this.__intersectionObserver = new IntersectionObserver(() => this.resizeTextarea());
this.__intersectionObserver.observe(this);
} }
/** @param {import('lit-element').PropertyValues } changedProperties */ /** @param {import('lit-element').PropertyValues } changedProperties */

View file

@ -1,4 +1,5 @@
import { expect, fixture as _fixture, html } from '@open-wc/testing'; import { expect, fixture as _fixture, html, nextFrame } from '@open-wc/testing';
import sinon from 'sinon';
import '../lion-textarea.js'; import '../lion-textarea.js';
/** /**
@ -147,6 +148,21 @@ describe('<lion-textarea>', () => {
expect(el._inputNode.getAttribute('placeholder')).to.equal('foo'); expect(el._inputNode.getAttribute('placeholder')).to.equal('foo');
}); });
it('fires resize textarea when a visibility change has been detected', async () => {
const el = await fixture(`
<div style="display: none">
<lion-textarea placeholder="text"></lion-textarea>
</div>
`);
const textArea = /** @type {LionTextarea} */ (el.firstElementChild);
await textArea.updateComplete;
const resizeSpy = sinon.spy(textArea, 'resizeTextarea');
el.style.display = 'block';
await nextFrame();
expect(resizeSpy.calledOnce).to.be.true;
});
it('is accessible', async () => { it('is accessible', async () => {
const el = await fixture(`<lion-textarea label="Label"></lion-textarea>`); const el = await fixture(`<lion-textarea label="Label"></lion-textarea>`);
await expect(el).to.be.accessible(); await expect(el).to.be.accessible();