Skip to content

Commit

Permalink
CSS-based solution to autosize textarea
Browse files Browse the repository at this point in the history
This (I hope) fixes the wonky scrolling described in #1038.

This is a backport of the first commit in PR #1050.  It omits all
the hairy bits required to enable manual drag-handle resizing of
textareas.

(I want to get this into the stable 3.3 branch, so that we can have
a working release.)

Based on ideas from this post:

https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/
  • Loading branch information
dairiki committed Jul 27, 2022
1 parent 707b1dc commit c3974e2
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 37 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ These are all the changes in Lektor since the first public release.

#### Admin Frontend

- Fix for spurious page scrolling when typing in textareas. ([#1038][], [#1050][])
- Make `size = {small|large}` field option work again. ([#1022][])
- Fix the _edit_ (`<ctl>-e`) hotkey. ([#1022][])
- Handle hotkeys when the preview iframe has the focus. ([#1022][])
Expand All @@ -26,6 +27,8 @@ These are all the changes in Lektor since the first public release.
Broken since Lektor 3.3.2 by PR [#1003][].

[#1022]: https://github.com/lektor/lektor/issues/1022
[#1038]: https://github.com/lektor/lektor/issues/1038
[#1050]: https://github.com/lektor/lektor/pull/1050

## 3.3.5 (2022-07-18)

Expand Down
54 changes: 23 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,31 @@ 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);
};

/* How this works
*
* The idea is ripped off from here:
*
* https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/
*
* We stack an (invisible) <div> under our <textarea> in a container
* with display: grid.
*
* The issue we are trying to solve is that <textarea>s do not expand
* when content is added to them. However, <div>s do!
*
* The contents of the <textarea> is duplicated to the <div>. The
* grid layout ensures that when the <div> expands, the <textarea> is
* expanded to match.
*/
return (
<div>
<div className="text-widget">
<div className="text-widget__replica">{value}</div>
<textarea
ref={textarea}
className={getInputClass(type)}
className={`${getInputClass(type)} text-widget__textarea`}
onChange={onChange}
value={value}
disabled={disabled}
Expand Down
36 changes: 30 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,36 @@ div.flow-block {
padding: 5px 10px;
}

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

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


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

overflow: hidden; // never any scrollbars
}

&__replica {
visibility: hidden;

&:after {
// adding an extra space prevents jank when text ends with newline
content: " ";
}
}

&__textarea {
resize: none;
}
}

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

0 comments on commit c3974e2

Please sign in to comment.