/* * Copyright 2020 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ import {chain, useLayoutEffect} from '@react-aria/utils'; import React, {Ref, useCallback, useEffect, useRef} from 'react'; import {SpectrumTextAreaProps, SpectrumTextFieldBaseProps, TextFieldRef} from '@react-types/textfield'; import {TextFieldBase} from './TextFieldBase'; import {useControlledState} from '@react-stately/utils'; import {useFormProps} from '@react-spectrum/form'; import {useProviderProps} from '@react-spectrum/provider'; import {useTextField} from '@react-aria/textfield'; /** * TextAreas are multiline text inputs, useful for cases where users have * a sizable amount of text to enter. They allow for all customizations that * are available to text fields. */ export const TextArea = React.forwardRef(function TextArea(props: SpectrumTextAreaProps, ref: Ref>) { props = useProviderProps(props); props = useFormProps(props); let { isDisabled = false, isQuiet = false, isReadOnly = false, isRequired = false, onChange, ...otherProps } = props; // not in stately because this is so we know when to re-measure, which is a spectrum design let [inputValue, setInputValue] = useControlledState(props.value, props.defaultValue ?? '', () => {}); let inputRef = useRef(null); let onHeightChange = useCallback(() => { // Quiet textareas always grow based on their text content. // Standard textareas also grow by default, unless an explicit height is set. if ((isQuiet || !props.height) && inputRef.current) { let input = inputRef.current; let prevAlignment = input.style.alignSelf; let prevOverflow = input.style.overflow; // Firefox scroll position is lost when overflow: 'hidden' is applied so we skip applying it. // The measure/applied height is also incorrect/reset if we turn on and off // overflow: hidden in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1787062 let isFirefox = 'MozAppearance' in input.style; if (!isFirefox) { input.style.overflow = 'hidden'; } input.style.alignSelf = 'start'; input.style.height = 'auto'; // offsetHeight - clientHeight accounts for the border/padding. input.style.height = `${input.scrollHeight + (input.offsetHeight - input.clientHeight)}px`; input.style.overflow = prevOverflow; input.style.alignSelf = prevAlignment; } }, [isQuiet, inputRef, props.height]); useLayoutEffect(() => { if (inputRef.current) { onHeightChange(); } }, [onHeightChange, inputValue, inputRef]); let hasWarned = useRef(false); useEffect(() => { if (props.placeholder && !hasWarned.current && process.env.NODE_ENV !== 'production') { console.warn('Placeholders are deprecated due to accessibility issues. Please use help text instead. See the docs for details: https://react-spectrum.adobe.com/react-spectrum/TextArea.html#help-text'); hasWarned.current = true; } }, [props.placeholder]); let result = useTextField({ ...props, onChange: chain(onChange, setInputValue), inputElementType: 'textarea' }, inputRef); return ( ); });