@syncfusion/react-inputs
Version:
Syncfusion React Input package is a feature-rich collection of UI components, including Textbox, Textarea, Numeric-textbox and Form, designed to capture user input in React applications.
252 lines (251 loc) • 10.8 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState, useLayoutEffect, useMemo } from 'react';
import { CLASS_NAMES, renderClearButton, renderFloatLabelElement, renderAdornmentElement } from '../common/inputbase';
import { preRender, useProviderContext, Variant, Size, useStableId } from '@syncfusion/react-base';
import { HelperText } from '../common/helper-text';
export { Variant, Size };
/**
* Constant for multi-line input class
*/
const MULTILINE = 'sf-multi-line-input';
/**
* Constant for auto-width class
*/
const AUTOWIDTH = 'sf-auto-width';
/**
* Specifies the available resize modes for components that support resizing.
*
* @enum {string}
*/
export var ResizeMode;
(function (ResizeMode) {
/**
* Disables resizing functionality.
*/
ResizeMode["None"] = "None";
/**
* Enables resizing in both horizontal and vertical directions.
*/
ResizeMode["Both"] = "Both";
/**
* Enables resizing only in the horizontal direction.
*/
ResizeMode["Horizontal"] = "Horizontal";
/**
* Enables resizing only in the vertical direction.
*/
ResizeMode["Vertical"] = "Vertical";
})(ResizeMode || (ResizeMode = {}));
const RESIZE_MAP = {
/**
* Constant for no resize mode
*/
[ResizeMode.None]: 'sf-resize-none',
/**
* Constant for both horizontal and vertical resize mode
*/
[ResizeMode.Both]: 'sf-resize-xy',
/**
* Constant for horizontal resize mode
*/
[ResizeMode.Horizontal]: 'sf-resize-x',
/**
* Constant for vertical resize mode
*/
[ResizeMode.Vertical]: 'sf-resize-y'
};
/**
* TextArea component that provides a multi-line text input field with enhanced functionality.
* Supports both controlled and uncontrolled modes based on presence of value or defaultValue prop.
*
* ```typescript
* import { TextArea } from '@syncfusion/react-inputs';
*
* <TextArea defaultValue="Initial text" placeholder="Enter text" rows={5} cols={40} />
* ```
*/
export const TextArea = forwardRef((props, ref) => {
const { readOnly = false, value, defaultValue, labelMode = 'Never', placeholder = '', disabled = false, width, resizeMode = ResizeMode.Both, maxLength, cols = null, rows = null, clearButton = false, autoResize = false, className = '', id, size = Size.Medium, variant, onChange, onBlur, onFocus, prefix, suffix, helperText, helperTextOnFocus = false, helperTextDirection = 'Left', ...rest } = props;
const isControlled = value !== undefined;
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue || '');
const displayValue = isControlled ? value : uncontrolledValue;
const [isFocused, setIsFocused] = useState(false);
const generatedId = useStableId('sf-textarea');
const textareaId = id ?? generatedId;
const elementRef = useRef(null);
const autoResizeFrameIdRef = useRef(null);
const resizeObserverInstanceRef = useRef(null);
const lastObservedInlineSizeRef = useRef(null);
const { locale, dir } = useProviderContext();
const publicAPI = {
clearButton,
labelMode,
disabled,
readOnly,
resizeMode,
helperText,
helperTextOnFocus,
helperTextDirection
};
useEffect(() => {
preRender('textarea');
}, []);
useEffect(() => {
if (isControlled) {
setUncontrolledValue(value || '');
}
}, [isControlled, value]);
const classNames = (...classes) => {
return classes.filter(Boolean).join(' ');
};
const containerClassNames = useMemo(() => classNames(CLASS_NAMES.INPUTGROUP, CLASS_NAMES.WRAPPER, labelMode !== 'Never' ? CLASS_NAMES.FLOATINPUT : '', MULTILINE, className, dir === 'rtl' ? CLASS_NAMES.RTL : '', disabled ? CLASS_NAMES.DISABLE : '', readOnly ? CLASS_NAMES.READONLY : '', isFocused ? CLASS_NAMES.TEXTBOX_FOCUS : '', displayValue !== '' ? CLASS_NAMES.VALIDINPUT : '', variant && variant.toLowerCase() !== 'standard'
? variant.toLowerCase() === 'outlined' ? 'sf-outline' : `sf-${variant.toLowerCase()}`
: '', AUTOWIDTH, size && size.toLowerCase() !== 'small' ? `sf-${size.toLowerCase()}` : '', prefix ? 'sf-has-prefix' : '', suffix ? 'sf-has-suffix' : '', 'sf-control'), [isFocused, displayValue, disabled, readOnly, labelMode, variant, size, prefix, suffix, dir, className]);
useImperativeHandle(ref, () => ({
...publicAPI,
element: elementRef.current
}));
const adjustHeight = useCallback((textAreaElement) => {
if (!textAreaElement) {
return;
}
textAreaElement.style.height = 'auto';
textAreaElement.style.height = `${textAreaElement.scrollHeight}px`;
}, []);
const requestAdjustHeight = useCallback(() => {
if (!autoResize || !elementRef.current) {
return;
}
if (autoResizeFrameIdRef.current !== null) {
cancelAnimationFrame(autoResizeFrameIdRef.current);
autoResizeFrameIdRef.current = null;
}
autoResizeFrameIdRef.current = requestAnimationFrame(() => {
adjustHeight(elementRef.current);
});
}, [autoResize, adjustHeight]);
const getObservedInlineSize = (entry) => {
const firstBox = Array.isArray(entry.contentBoxSize)
? entry.contentBoxSize[0]
: entry.contentBoxSize;
if (firstBox && typeof firstBox.inlineSize === 'number') {
return firstBox.inlineSize;
}
const width = entry.contentRect.width;
return typeof width === 'number' ? width : null;
};
useEffect(() => {
return () => {
if (autoResizeFrameIdRef.current !== null) {
cancelAnimationFrame(autoResizeFrameIdRef.current);
autoResizeFrameIdRef.current = null;
}
};
}, []);
useEffect(() => {
if (!autoResize || !elementRef.current) {
return;
}
let frameScheduled = false;
const scheduleAdjustHeight = () => {
if (frameScheduled) {
return;
}
frameScheduled = true;
requestAnimationFrame(() => {
frameScheduled = false;
if (!elementRef.current) {
return;
}
requestAdjustHeight();
});
};
if ('ResizeObserver' in window) {
const handleObservedSizeChange = (entries) => {
const entry = entries[0];
if (!entry) {
return;
}
const inlineSize = getObservedInlineSize(entry);
if (inlineSize !== null && lastObservedInlineSizeRef.current === inlineSize) {
return;
}
lastObservedInlineSizeRef.current = inlineSize;
scheduleAdjustHeight();
};
const observer = new ResizeObserver(handleObservedSizeChange);
observer.observe(elementRef.current);
resizeObserverInstanceRef.current = observer;
return () => {
observer.disconnect();
resizeObserverInstanceRef.current = null;
lastObservedInlineSizeRef.current = null;
};
}
const view = elementRef.current.ownerDocument ? elementRef.current.ownerDocument.defaultView : null;
if (!view) {
return;
}
const handleWindowResize = () => {
scheduleAdjustHeight();
};
view.addEventListener('resize', handleWindowResize);
return () => {
view.removeEventListener('resize', handleWindowResize);
};
}, [autoResize, requestAdjustHeight]);
useLayoutEffect(() => {
if (!autoResize || !elementRef.current) {
return;
}
requestAdjustHeight();
}, [autoResize, displayValue, width, rows, cols]);
const handleChange = useCallback((event) => {
const newValue = event.target.value;
if (!isControlled) {
setUncontrolledValue(newValue);
}
if (onChange) {
onChange({ event, value: newValue });
}
if (autoResize) {
requestAdjustHeight();
}
}, [isControlled, onChange, uncontrolledValue, value, autoResize, requestAdjustHeight]);
const handleFocus = useCallback((event) => {
setIsFocused(true);
if (onFocus) {
onFocus(event);
}
}, [onFocus]);
const handleBlur = useCallback((event) => {
setIsFocused(false);
if (onBlur) {
onBlur(event);
}
}, [onBlur]);
const clearValue = useCallback(() => {
const newValue = '';
if (!isControlled) {
setUncontrolledValue(newValue);
if (elementRef.current) {
elementRef.current.value = newValue;
}
}
if (onChange) {
onChange({ value: newValue, event: undefined });
}
if (autoResize) {
requestAdjustHeight();
}
}, [onChange, isControlled, requestAdjustHeight]);
const getCurrentResizeClass = (resizeMode) => {
return RESIZE_MAP[`${resizeMode}`];
};
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: containerClassNames, children: [renderAdornmentElement(prefix), _jsx("textarea", { ref: elementRef, id: textareaId, value: isControlled ? (value) : undefined, defaultValue: !isControlled ? (defaultValue) : undefined, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, readOnly: readOnly, placeholder: labelMode === 'Never' ? placeholder : undefined, disabled: disabled, maxLength: maxLength, cols: cols ?? undefined, rows: rows ?? undefined, ...rest, style: {
width: width ? (typeof width === 'number' ? `${width}px` : width) : undefined,
resize: resizeMode === 'None' || autoResize ? 'none' : undefined,
overflowY: autoResize ? 'hidden' : undefined
}, className: `sf-textarea sf-lib sf-input ${getCurrentResizeClass(resizeMode)}`, "aria-labelledby": `label_${textareaId}` }), renderFloatLabelElement(labelMode, isFocused || (displayValue) !== '', displayValue, placeholder, textareaId), clearButton && renderClearButton((displayValue && isFocused) ? (displayValue).toString() : '', clearValue, clearButton, 'textarea', locale), renderAdornmentElement(suffix)] }), _jsx(HelperText, { helperText: helperText, helperTextOnFocus: helperTextOnFocus, isFocused: isFocused, helperTextDirection: helperTextDirection })] }));
});
export default TextArea;