@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
365 lines (364 loc) • 12.5 kB
JavaScript
"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