Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React from 'react';
import TextInput from '..';
import { createEventTarget } from 'dom-event-testing-library';
import { act, render } from '@testing-library/react';
import { act, fireEvent, render } from '@testing-library/react';

function findInput(container) {
return container.querySelector('input');
Expand Down Expand Up @@ -333,6 +333,53 @@ describe('components/TextInput', () => {
expect(onChange).toHaveBeenCalledTimes(1);
});

describe('prop "onContentSizeChange"', () => {
// jsdom has no layout, so report a height that only depends on the
// content (like a zero-height textarea would) and no width.
let scrollHeight;
beforeEach(() => {
scrollHeight = jest
.spyOn(HTMLTextAreaElement.prototype, 'scrollHeight', 'get')
.mockImplementation(function () {
return this.style.height === '0px'
? this.value.split('\n').length * 10
: 0;
});
});
afterEach(() => {
scrollHeight.mockRestore();
});

test('is called when the content shrinks', () => {
const onContentSizeChange = jest.fn();
const { container } = render(
<TextInput
multiline
onContentSizeChange={onContentSizeChange}
style={{ height: 100 }}
/>
);
const textarea = findTextArea(container);
expect(onContentSizeChange).toHaveBeenCalledTimes(1);
expect(onContentSizeChange).toHaveBeenLastCalledWith({
nativeEvent: { contentSize: { height: 10, width: 0 } }
});
fireEvent.change(textarea, { target: { value: 'a\nb\nc' } });
expect(onContentSizeChange).toHaveBeenCalledTimes(2);
expect(onContentSizeChange).toHaveBeenLastCalledWith({
nativeEvent: { contentSize: { height: 30, width: 0 } }
});
fireEvent.change(textarea, { target: { value: 'a' } });
expect(onContentSizeChange).toHaveBeenCalledTimes(3);
expect(onContentSizeChange).toHaveBeenLastCalledWith({
nativeEvent: { contentSize: { height: 10, width: 0 } }
});
// the textarea itself is left untouched
expect(textarea.style.height).toBe('100px');
expect(container.querySelectorAll('textarea')).toHaveLength(1);
});
});

test.skip('prop "onChangeText"', () => {
const onChangeText = jest.fn();
const { container } = render(<TextInput onChangeText={onChangeText} />);
Expand Down
29 changes: 27 additions & 2 deletions packages/react-native-web/src/exports/TextInput/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,31 @@ function isEventComposing(nativeEvent) {

let focusTimeout: ?TimeoutID = null;

/**
* The scrollHeight of a textarea is never less than its own height, so it
* can't be used to detect that the content has become smaller than the box.
* Measure a zero-height copy instead so the box the textarea has been given
* (and any scroll position clamped to it) is left alone.
*/
function measureContentSize(hostNode) {
const clone = hostNode.cloneNode();
clone.value = hostNode.value;
// Keep the same wrapping width, but take the copy out of the layout.
clone.style.width = window.getComputedStyle(hostNode).width;
clone.style.height = '0';
clone.style.minHeight = '0';
clone.style.maxHeight = 'none';
clone.style.overflow = 'hidden';
clone.style.position = 'fixed';
clone.style.top = '0';
clone.style.left = '0';
clone.style.visibility = 'hidden';
hostNode.after(clone);
const size = { height: clone.scrollHeight, width: clone.scrollWidth };
clone.remove();
return size;
}

const TextInput: React.AbstractComponent<
TextInputProps,
HTMLElement & PlatformMethods
Expand Down Expand Up @@ -209,8 +234,8 @@ const TextInput: React.AbstractComponent<
const handleContentSizeChange = React.useCallback(
(hostNode) => {
if (multiline && onContentSizeChange && hostNode != null) {
const newHeight = hostNode.scrollHeight;
const newWidth = hostNode.scrollWidth;
const { height: newHeight, width: newWidth } =
measureContentSize(hostNode);
if (
newHeight !== dimensions.current.height ||
newWidth !== dimensions.current.width
Expand Down