UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

365 lines (364 loc) 12.5 kB
"use client"; var _AlignmentHelper, _span; import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js"; import React, { useCallback, useContext, useMemo, useRef, useState } from 'react'; import useMountEffect from "../../shared/helpers/useMountEffect.js"; import useCombinedRef from "../../shared/helpers/useCombinedRef.js"; import clsx from 'clsx'; import FormLabel from "../form-label/FormLabel.js"; import FormStatus from "../form-status/FormStatus.js"; import TextCounter from "../../fragments/text-counter/TextCounter.js"; import useId from "../../shared/helpers/useId.js"; import { extendPropsWithContext, removeUndefinedProps, validateDOMAttributes, processChildren, getStatusState, combineDescribedBy, warn, dispatchCustomElementEvent, convertJsxToString } from "../../shared/component-helper.js"; import { pickFormElementProps } from "../../shared/helpers/filterValidProps.js"; import AlignmentHelper from "../../shared/AlignmentHelper.js"; import { applySpacing } from "../space/SpacingUtils.js"; import { skeletonDOMAttributes, createSkeletonClass } from "../skeleton/SkeletonHelper.js"; import Context from "../../shared/Context.js"; import Suffix from "../../shared/helpers/Suffix.js"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const textareaDefaultProps = { value: 'initval', statusState: 'error', readOnly: false, labelDirection: 'vertical' }; function hasValue(value) { return (typeof value === 'string' || typeof value === 'number') && String(value).length > 0 || false; } function getValue(props) { const value = processChildren(props); if (value === '' || hasValue(value)) { return value; } return props.value; } function getResizeModifier() { try { if (typeof navigator !== 'undefined') { if (/Firefox|Edg/.test(navigator.userAgent) || /Chrome/.test(navigator.userAgent) && /Win/.test(navigator.platform)) { return 'large'; } if (/Safari|Chrome/.test(navigator.userAgent) && /Mac/.test(navigator.platform)) { return 'medium'; } } } catch (error) { console.error(error); } return false; } export function TextareaComponent({ ref, ...ownProps }) { var _context$getTranslati; const context = useContext(Context); const props = extendPropsWithContext({ ...textareaDefaultProps, ...removeUndefinedProps({ ...ownProps }) }, textareaDefaultProps, { skeleton: context === null || context === void 0 ? void 0 : context.skeleton }, (_context$getTranslati = context.getTranslation(ownProps)) === null || _context$getTranslati === void 0 ? void 0 : _context$getTranslati.Textarea, pickFormElementProps(context === null || context === void 0 ? void 0 : context.formElement), context === null || context === void 0 ? void 0 : context.Textarea); const { label, labelDirection, labelSrOnly, status, statusState, statusProps, statusNoAnimation, globalStatus, suffix, disabled, skeleton, stretch, placeholder, keepPlaceholder, align, size, textareaClassName, readOnly, className, autoResize, characterCounter, autoResizeMaxRows, id: _id, children: _children, value: _value, textareaElement: _textareaElement, ref: _ref, ...attributes } = props; const textareaRef = useRef(null); const combinedRef = useCombinedRef(ref, textareaRef); const id = useId(ownProps.id); const resizeModifier = useMemo(() => getResizeModifier(), []); const heightOffsetRef = useRef(undefined); const resizeObserverRef = useRef(null); const propValue = getValue(ownProps); const prevValuePropRef = useRef(propValue); const [value, setValue] = useState(() => { if (propValue !== 'initval' && propValue !== null) { return propValue; } return null; }); const [textareaState, setTextareaState] = useState(() => { return ownProps.textareaState || 'virgin'; }); if (propValue !== 'initval' && propValue !== value && propValue !== prevValuePropRef.current) { setValue(propValue); } prevValuePropRef.current = propValue; if (ownProps.textareaState && ownProps.textareaState !== textareaState) { setTextareaState(ownProps.textareaState); } const getLineHeight = useCallback(() => { return parseFloat(getComputedStyle(textareaRef.current).lineHeight) || 0; }, []); const getRows = useCallback(() => { return Math.floor(textareaRef.current.scrollHeight / getLineHeight()) || 1; }, [getLineHeight]); const prepareAutosize = useCallback(() => { const elem = textareaRef.current; if (!elem) { return; } try { elem.style.height = 'auto'; } catch (e) { warn(e); } }, []); const setAutosize = useCallback((rows = null) => { const elem = textareaRef.current; if (!elem) { return; } try { if (typeof heightOffsetRef.current === 'undefined') { heightOffsetRef.current = elem.offsetHeight - elem.clientHeight; } elem.style.height = 'auto'; const lineHeight = getLineHeight(); let newHeight = elem.scrollHeight + heightOffsetRef.current; if (!rows) { rows = getRows(); } if (rows === 1) { if (newHeight > lineHeight) { newHeight = lineHeight; } } const maxRows = parseFloat(String(autoResizeMaxRows)); if (maxRows > 0) { const maxHeight = maxRows * lineHeight; if (rows > maxRows || newHeight > maxHeight) { newHeight = maxHeight; } } elem.style.height = newHeight + 'px'; } catch (e) { warn(e); } }, [autoResizeMaxRows, getLineHeight, getRows]); const onFocusHandler = useCallback(event => { const { value } = textareaRef.current; setValue(value); setTextareaState('focus'); dispatchCustomElementEvent(props, 'onFocus', { value, event }); }, [props.onFocus]); const onBlurHandler = useCallback(event => { const { value } = event.target; setValue(value); setTextareaState(hasValue(value) ? 'dirty' : 'initial'); dispatchCustomElementEvent(props, 'onBlur', { value, event }); }, [props.onBlur]); const onChangeHandler = useCallback(event => { const { value } = event.target; if (autoResize) { prepareAutosize(); } const rows = getRows(); const ret = dispatchCustomElementEvent(props, 'onChange', { value, rows, event }); if (ret !== false) { setValue(value); if (autoResize) { setAutosize(rows); } } }, [autoResize, prepareAutosize, getRows, setAutosize, props.onChange]); const onKeyDownHandler = useCallback(event => { const rows = getRows(); const { value } = event.target; dispatchCustomElementEvent(props, 'onKeyDown', { value, rows, event }); }, [getRows, props.onKeyDown]); const setAutosizeRef = useRef(setAutosize); setAutosizeRef.current = setAutosize; useMountEffect(() => { const handleResize = () => setAutosizeRef.current(); if (autoResize && typeof window !== 'undefined') { setAutosizeRef.current(); try { const observer = new ResizeObserver(entries => { window.requestAnimationFrame(() => { if (!Array.isArray(entries) || !entries.length) { return; } setAutosizeRef.current(); }); }); observer.observe(document.body); resizeObserverRef.current = observer; } catch (e) { window.addEventListener('resize', handleResize); } } return () => { if (resizeObserverRef.current) { resizeObserverRef.current.disconnect(); resizeObserverRef.current = null; } if (typeof window !== 'undefined') { window.removeEventListener('resize', handleResize); } }; }); const showStatus = getStatusState(status); const currentHasValue = hasValue(value); let TextareaElement = props.textareaElement; const textareaParams = { className: clsx("dnb-textarea__textarea dnb-input__border", textareaClassName), role: 'textbox', value: currentHasValue ? value : '', id, name: id, disabled: disabled || skeleton, 'aria-placeholder': placeholder ? convertJsxToString(placeholder) : undefined, ...attributes, ...(typeof size === 'number' ? { size } : {}), onChange: onChangeHandler, onFocus: onFocusHandler, onBlur: onBlurHandler, onKeyDown: onKeyDownHandler }; if (showStatus || suffix) { textareaParams['aria-describedby'] = combineDescribedBy(textareaParams, showStatus ? id + '-status' : null, suffix ? id + '-suffix' : null); } if (readOnly) { textareaParams['aria-readonly'] = textareaParams.readOnly = true; } const mainParams = applySpacing(props, { className: clsx(`dnb-textarea dnb-textarea--${textareaState} dnb-form-component`, createSkeletonClass(null, skeleton), className, autoResize ? 'dnb-textarea__autoresize' : resizeModifier && `dnb-textarea__resize--${resizeModifier}`, disabled && 'dnb-textarea--disabled', currentHasValue && 'dnb-textarea--has-content', align && `dnb-textarea__align--${align}`, typeof size === 'string' && `dnb-textarea__size--${size}`, status && `dnb-textarea__status--${statusState}`, labelDirection && `dnb-textarea--${labelDirection}`, stretch && `dnb-textarea--stretch`, keepPlaceholder && `dnb-textarea--keep-placeholder`) }); const innerParams = { className: clsx('dnb-textarea__inner', createSkeletonClass('shape', skeleton, context)) }; const shellParams = { className: 'dnb-textarea__shell' }; if (disabled || skeleton) { shellParams['aria-disabled'] = true; } const placeholderStyle = parseFloat(String(props.rows)) > 0 ? { '--textarea-rows': parseFloat(String(props.rows)) } : null; skeletonDOMAttributes(innerParams, skeleton, context); validateDOMAttributes(ownProps, textareaParams); validateDOMAttributes(null, innerParams); validateDOMAttributes(null, shellParams); if (TextareaElement && typeof TextareaElement === 'function') { TextareaElement = TextareaElement(textareaParams, textareaRef); } else if (!TextareaElement && _textareaElement) { TextareaElement = _textareaElement; } return _jsxs("span", { ...mainParams, children: [label && _jsx(FormLabel, { id: id + '-label', forId: id, text: label, labelDirection: labelDirection, srOnly: labelSrOnly, disabled: disabled, skeleton: skeleton }), _jsxs("span", { ...innerParams, children: [_AlignmentHelper || (_AlignmentHelper = _jsx(AlignmentHelper, {})), _jsx(FormStatus, { show: showStatus, id: id + '-form-status', globalStatus: globalStatus, label: label, textId: id + '-status', text: status, state: statusState, noAnimation: statusNoAnimation, skeleton: skeleton, ...statusProps }), _jsxs("span", { className: "dnb-textarea__row", children: [_jsxs("span", { ...shellParams, children: [TextareaElement || _jsx("textarea", { ref: combinedRef, ...textareaParams }), !currentHasValue && placeholder && (textareaState !== 'focus' || keepPlaceholder) && _jsx("span", { className: 'dnb-textarea__placeholder' + (align ? ` dnb-textarea__align--${align}` : ""), style: placeholderStyle, "aria-hidden": true, children: placeholder }), _span || (_span = _jsx("span", { className: "dnb-textarea__state" }))] }), suffix && _jsx(Suffix, { className: "dnb-textarea__suffix", id: id + '-suffix', context: props, children: suffix })] }), characterCounter && _jsx(TextCounter, { top: "x-small", text: value, max: characterCounter, lang: props.lang, locale: props.locale, ...(typeof characterCounter === 'object' ? characterCounter : {}) })] })] }); } TextareaComponent.displayName = 'Textarea'; const MemoizedTextarea = React.memo(TextareaComponent); withComponentMarkers(MemoizedTextarea, { _formElement: true, _supportsSpacingProps: true }); export default MemoizedTextarea; //# sourceMappingURL=Textarea.js.map