Skip to content

Commit

Permalink
CSS-based solution to autosize textarea
Browse files Browse the repository at this point in the history
This is yet another try at fixing the wonky scrolling described in lektor#1038.

Based on ideas from this post:

https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/
  • Loading branch information
dairiki committed Jul 25, 2022
1 parent 7eff923 commit 8027d91
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 37 deletions.
48 changes: 17 additions & 31 deletions frontend/js/widgets/MultiLineTextInputWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ChangeEvent, useCallback, useEffect, useRef } from "react";
import React, { ChangeEvent } from "react";
import { getInputClass, WidgetProps } from "./types";

export function MultiLineTextInputWidget({
Expand All @@ -8,39 +8,25 @@ export function MultiLineTextInputWidget({
disabled,
onChange: onChangeProp,
}: WidgetProps) {
const textarea = useRef<HTMLTextAreaElement | null>(null);

const recalculateSize = useCallback(() => {
const node = textarea.current;
if (node) {
node.style.height = "auto";
node.style.height = node.scrollHeight + "px";
}
}, []);

const onChange = useCallback(
(event: ChangeEvent<HTMLTextAreaElement>) => {
onChangeProp(event.target.value);
},
[onChangeProp]
);

useEffect(() => {
recalculateSize();
}, [recalculateSize, value]);

useEffect(() => {
window.addEventListener("resize", recalculateSize);
return () => {
window.removeEventListener("resize", recalculateSize);
};
}, [recalculateSize]);
const onChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
onChangeProp(event.target.value);
};

// See autosize-textarea.scss for comments on how the autosizing works
//
// NB: For reasons not understood, a funny page scroll on the first
// (but only the first) textarea edit sometimes happens if the hidden
// div comes after the textarea.
return (
<div>
<div className="multiline-text-widget">
<div className="multiline-text-widget__replica">
{
value +
" " /* extra space prevents jank when text ends with newline */
}
</div>
<textarea
ref={textarea}
className={getInputClass(type)}
className={getInputClass(type) + " multiline-text-widget__textarea"}
onChange={onChange}
value={value}
disabled={disabled}
Expand Down
29 changes: 23 additions & 6 deletions frontend/scss/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ dl.field {
color: $tab-header-color;
padding: 2px 8px;
}

textarea {
display: block;
overflow: hidden;
resize: none;
}
}

.system-fields {
Expand Down Expand Up @@ -92,6 +86,29 @@ div.flow-block {
padding: 5px 10px;
}

.multiline-text-widget {
display: grid;
contain: layout;

&__replica {
visibility: hidden;
}

&__textarea {
resize: none;
}

&__replica,
&__textarea {
grid-area: 1 / 1 / 2 / 2; // Place them on top of each other
overflow: hidden; // never any scrollbars

// Ensure they get identical styling
@extend .form-control;
white-space: pre-wrap; // How textarea wraps
}
}

/* fake types */
.info-widget {
border: 1px solid $panel-border-color;
Expand Down

0 comments on commit 8027d91

Please sign in to comment.