bug(cdkTextareaAutosize): autosize scrolls parent to top at each key hit with ngZoneEventCoalescing enabled
Reproduction
- open in chrome https://stackblitz.com/edit/angular-dqeq92?file=src%2Fapp%2Fdialog-content-example.html
- type a few line breaks or texts into the textarea until the the div's scrollbar appear
- scroll to the bottom and type more line breaks or text
Expected Behavior
The scroll position of the div should not be changed.
Actual Behavior
After each key hit the div gets scrolled to top. So its not possible to see what is currently typed.
Environment
- Angular: 12.2.11
- CDK/Material: 12.2.9
- Browser(s): Chrome, Edge (Chromium), Opera
- Operating System: Windows
When ngZoneEventCoalescing is set to false, it works in Chrome as expected.
It seems like its related to #23233. In firefox it works as expected.
When the firefox work around in the autosize Directive is used for chrome too, then it works fine. But I'm not really know, if it has some other side effects when using the work around for chrome too. https://github.com/angular/components/blob/a931de54a786597b34259e461c2cf3ab6edc590a/src/cdk/text-field/autosize.ts#L227-L237
I was able to reproduce the issue, but I don't know if we can apply the margin trick from Firefox, because we use a different class while the textarea is being measured (see https://github.com/angular/components/blob/master/src/cdk/text-field/_index.scss#L9). The reason it works for Firefox is that the element also has height: 0.
I tried it out in another stackblitz with a custom autosize directive where the isFirefox variable is set to true. So that the firefox class and the margin trick gets used. https://stackblitz.com/edit/angular-dqeq92-vdymoj?file=src%2Fapp%2Fcustom-autosize.ts
const isFirefox = (true || this._platform.FIREFOX);
const needsMarginFiller = isFirefox && this._hasFocus;
const measuringClass = isFirefox
? 'cdk-textarea-autosize-measuring-firefox'
: 'cdk-textarea-autosize-measuring';
// In some cases the page might move around while we're measuring the `textarea` on Firefox. We
// work around it by assigning a temporary margin with the same height as the `textarea` so that
// it occupies the same amount of space. See #23233.
if (needsMarginFiller) {
element.style.marginBottom = `${element.clientHeight}px`;
}
then the scrolling works better, so its not getting scrolled to top.
I also tried it out without the firefox class and in this case it also works without it.
The question is whether we can safely apply the Firefox class everywhere. The .scss file I linked above has some comments that the height: 0 causes issues in Chrome and IE. We don't have to worry about IE anymore, but Chrome could still be a problem.
do we need to use the firefox class?
For Chrome it works without setting the height: 0 so there is no need to use it, or?
Wouldn't that cause content below the input to jump? What would happen is that the textearea will become height: auto and margin-top: {{previousHeight}}.
I also have a problem on Chrome with that. Creating my own copy of the directive and disabling Firefox checking (actually the needsMarginFiller condition) solved the problem for me.
Wouldn't that cause content below the input to jump? What would happen is that the
texteareawill becomeheight: autoandmargin-top: {{previousHeight}}.
@crisbeto Correct me if I'm wrong, but I think you could disable this checking and content will not jump, because these changes take place in one cycle of the event loop.
I still have a problem on Chrome Any update on this?
I have fixed this by temporary set the parent's min-height to its offsetHeight before adding the measuring class:
const previousParentMinHeight = element.parentElement.style.minHeight;
element.parentElement.style.minHeight = `${element.parentElement.offsetHeight}px`;
// Reset the textarea height to auto in order to shrink back to its default size.
// Also temporarily force overflow:hidden, so scroll bars do not interfere with calculations.
element.classList.add(measuringClass);
// The measuring class includes a 2px padding to workaround an issue with Chrome,
// so we account for that extra space here by subtracting 4 (2px top + 2px bottom).
const scrollHeight = element.scrollHeight;
element.classList.remove(measuringClass);
element.parentElement.style.minHeight = previousParentMinHeight;
It worked perfectly with me. Is there any concerns about this fix?
If not, I can submit a pull request for it.
Thanks.