Skip to content

Commit

Permalink
fix: text area resizes properly every time it becomes visible (#374)
Browse files Browse the repository at this point in the history
* fix: text area resizes properly every time it becomes visible

Closes #314
  • Loading branch information
jarekdanielak authored Jul 19, 2024
1 parent 1e5849e commit b5eeced
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 9 deletions.
9 changes: 8 additions & 1 deletion src/components/entries/TextArea.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import classnames from 'classnames';

import {
useError,
useShowEntryEvent
useShowEntryEvent,
useElementVisible,
} from '../../hooks';

import { isFunction } from 'min-dash';
Expand Down Expand Up @@ -47,6 +48,8 @@ function TextArea(props) {

const ref = useShowEntryEvent(id);

const visible = useElementVisible(ref.current);

const handleInputCallback = useMemo(() => {
return debounce((target) => onInput(target.value.length ? target.value : undefined));
}, [ onInput, debounce ]);
Expand All @@ -63,6 +66,10 @@ function TextArea(props) {
autoResize && resizeToContents(ref.current);
}, []);

useLayoutEffect(() => {
visible && autoResize && resizeToContents(ref.current);
}, [ visible ]);

useEffect(() => {
if (value === localValue) {
return;
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export { usePrevious } from './usePrevious';
export { useShowEntryEvent } from './useShowEntryEvent';
export { useStickyIntersectionObserver } from './useStickyIntersectionObserver';
export { useStaticCallback } from './useStaticCallback';
export { useTooltipContext } from './useTooltipContext';
export { useTooltipContext } from './useTooltipContext';
export { useElementVisible } from './useElementVisible';
25 changes: 25 additions & 0 deletions src/hooks/useElementVisible.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useLayoutEffect, useState } from 'preact/hooks';

export function useElementVisible(element) {

const [ visible, setVisible ] = useState(!!element && !!element.clientHeight);

useLayoutEffect(() => {
if (!element) return;

const resizeObserver = new ResizeObserver(([ entry ]) => {
requestAnimationFrame(() => {
const newVisible = !!entry.contentRect.height;
if (newVisible !== visible) {
setVisible(newVisible);
}
});
});

resizeObserver.observe(element);

return () => resizeObserver.disconnect();
}, [ element, visible ]);

return visible;
}
48 changes: 41 additions & 7 deletions test/spec/components/TextArea.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
render
} from '@testing-library/preact/pure';

import { waitFor } from '@testing-library/preact';

import TestContainer from 'mocha-test-container-support';

import {
Expand Down Expand Up @@ -435,19 +437,18 @@ describe('<TextArea>', function() {

describe('auto resize', function() {

it('should resize initially', function() {
it('should resize initially', async function() {

// given
const result = createTextArea({
container,
id: 'textarea',
getValue() {
return `
HALLO
WELT
WIE
GEHTS
`;
1
2
3
4`;
},
autoResize: true
});
Expand All @@ -458,7 +459,9 @@ GEHTS
const initialHeight = input.clientHeight;

// then
expect(initialHeight).to.be.greaterThan(60);
await waitFor(() => {
expect(initialHeight).to.be.greaterThan(60);
});
});


Expand Down Expand Up @@ -517,6 +520,37 @@ GEHTS
expect(input.clientHeight).to.be.lessThan(35);
});


it('should resize when becomes visible', async function() {

// given
const result = createTextArea({
container,
id: 'textarea',
autoResize: true,
});

const input = domQuery('.bio-properties-panel-input', result.container);
const initialHeight = input.clientHeight;

// when
changeInput(input, 'foo\nbar\nbar\nbar');
result.container.style.display = 'none';
const hiddenHeight = input.clientHeight;

// then
expect(hiddenHeight).to.be.eq(0);

// when
result.container.style.display = 'block';
const visibleHeight = input.clientHeight;

// then
await waitFor(() => {
expect(visibleHeight).to.be.greaterThan(initialHeight * 2);
});
});

});


Expand Down
109 changes: 109 additions & 0 deletions test/spec/hooks/useElementVisible.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { renderHook } from '@testing-library/preact-hooks';

import { waitFor } from '@testing-library/preact';

import { useElementVisible } from 'src/hooks';

import TestContainer from 'mocha-test-container-support';


describe('hooks/useElementVisible', function() {

let container;
beforeEach(function() {
container = TestContainer.get(this);
});


it('should return true for visible element', async function() {

// given
const element = global.document.createElement('div');
container.appendChild(element);

// when
const { result } = renderHook(() => useElementVisible(element));

// then
expect(result.current).to.be.true;
});


it('should return false for not visible element', async function() {

// given
const element = global.document.createElement('div');

// when
const { result } = renderHook(() => useElementVisible(element));

// then
expect(result.current).to.be.false;
});


it('should return false when element does not exist', async function() {

// given
const element = undefined;

// when
const { result } = renderHook(() => useElementVisible(element));

// then
expect(result.current).to.be.false;
});


it('should return true when element becomes visible', async function() {

// given
const element = global.document.createElement('div');
container.appendChild(element);
element.style.display = 'none';

// when
const { rerender, result } = renderHook(() => useElementVisible(element));

// then
await waitFor(() => {
rerender();
expect(result.current).to.be.false;
});

// when
element.style.display = 'block';

// then
await waitFor(() => {
rerender();
expect(result.current).to.be.true;
});
});


it('should return false when element becomes hidden', async function() {

// given
const element = global.document.createElement('div');
container.appendChild(element);

// when
const { rerender, result } = renderHook(() => useElementVisible(element));

// then
await waitFor(() => {
rerender();
expect(result.current).to.be.true;
});

// when
element.style.display = 'none';

// then
await waitFor(() => {
rerender();
expect(result.current).to.be.false;
});
});
});

0 comments on commit b5eeced

Please sign in to comment.