UNPKG

@react-md/form

Version:

This package is for creating all the different form input types.

172 lines 10.4 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { forwardRef, useCallback, useEffect, useRef, useState } from "react"; import cn from "classnames"; import { bem, useEnsuredRef, useResizeObserver } from "@react-md/utils"; import { useFormTheme } from "../FormThemeProvider"; import { FloatingLabel } from "../label/FloatingLabel"; import { useFieldStates } from "../useFieldStates"; import { TextFieldContainer } from "./TextFieldContainer"; var block = bem("rmd-textarea"); var container = bem("rmd-textarea-container"); var PADDING_VARIABLES = "var(--rmd-form-text-padding-top, 0px) + var(--rmd-form-textarea-padding, 0px)"; // this is the default of 1.5rem line-height in the styles var DEFAULT_LINE_HEIGHT = "24"; export var TextArea = forwardRef(function TextArea(_a, forwardedRef) { var style = _a.style, className = _a.className, areaStyle = _a.areaStyle, areaClassName = _a.areaClassName, containerRef = _a.containerRef, containerProps = _a.containerProps, label = _a.label, labelStyle = _a.labelStyle, labelClassName = _a.labelClassName, _b = _a.rows, rows = _b === void 0 ? 2 : _b, _c = _a.maxRows, maxRows = _c === void 0 ? -1 : _c, _d = _a.resize, resize = _d === void 0 ? "auto" : _d, propTheme = _a.theme, _e = _a.dense, dense = _e === void 0 ? false : _e, _f = _a.inline, propInline = _f === void 0 ? false : _f, _g = _a.error, error = _g === void 0 ? false : _g, _h = _a.stretch, stretch = _h === void 0 ? false : _h, _j = _a.disabled, disabled = _j === void 0 ? false : _j, _k = _a.animate, animate = _k === void 0 ? true : _k, _l = _a.isLeftAddon, isLeftAddon = _l === void 0 ? true : _l, _m = _a.isRightAddon, isRightAddon = _m === void 0 ? true : _m, propUnderlineDirection = _a.underlineDirection, propOnBlur = _a.onBlur, propOnFocus = _a.onFocus, propOnChange = _a.onChange, leftChildren = _a.leftChildren, rightChildren = _a.rightChildren, props = __rest(_a, ["style", "className", "areaStyle", "areaClassName", "containerRef", "containerProps", "label", "labelStyle", "labelClassName", "rows", "maxRows", "resize", "theme", "dense", "inline", "error", "stretch", "disabled", "animate", "isLeftAddon", "isRightAddon", "underlineDirection", "onBlur", "onFocus", "onChange", "leftChildren", "rightChildren"]); var id = props.id, value = props.value, defaultValue = props.defaultValue; var _o = useFormTheme({ theme: propTheme, underlineDirection: propUnderlineDirection, }), theme = _o.theme, underlineDirection = _o.underlineDirection; var _p = __read(useState(), 2), height = _p[0], setHeight = _p[1]; useEffect(function () { if (resize !== "auto" && typeof height === "number") { setHeight(undefined); } }, [resize, height]); var maskRef = useRef(null); var _q = __read(useState(false), 2), scrollable = _q[0], setScrollable = _q[1]; var updateHeight = useCallback(function () { var mask = maskRef.current; /* istanbul ignore if */ if (!mask) { return; } var nextHeight = mask.scrollHeight; /* istanbul ignore if */ if (maxRows > 0) { var lineHeight = parseFloat(window.getComputedStyle(mask).lineHeight || DEFAULT_LINE_HEIGHT); var maxHeight = maxRows * lineHeight; nextHeight = Math.min(maxHeight, nextHeight); // only want the textarea to be scrollable if there's a limit on the rows // since it'll flash the scrollbar on most OS during the height transition if (nextHeight === maxHeight && !scrollable) { setScrollable(true); } else if (nextHeight !== maxHeight && scrollable) { setScrollable(false); } } if (height !== nextHeight) { setHeight(nextHeight); } }, [height, maxRows, scrollable]); // the window can be resized while there is text inside the textarea so need to // recalculate the height when the width changes as well. var _r = __read(useResizeObserver(updateHeight, { ref: maskRef, disableHeight: true, }), 2), maskRefHandler = _r[1]; var _s = useFieldStates({ onBlur: propOnBlur, onFocus: propOnFocus, onChange: function (event) { var mask = maskRef.current; if (propOnChange) { propOnChange(event); } /* istanbul ignore if */ if (!mask || resize !== "auto") { return; } // to get the height transition to work, you have to set the height on: // - the main container element (including padding) that has the height // transition enabled // - a child div wrapper (without padding) that has the height transition // enabled // - the textarea element (without padding) and without a height transition // // if it isn't done this way, the height transition will look weird since // the text will be fixed to the bottom of the area and more text at the top // will become visible as the height transition completes. applying the // transition on the two parent elements work because: // - the height is set immediately on the text field so it expands to show all // the text // - the height is correctly applied to both parent elements, but their height // haven't fully been adjusted due to the animation // - the parent divs have overflow visible by default, so the textarea's text // will expand past the boundaries of the divs and not cause the upwards // animation weirdness. mask.value = event.currentTarget.value; updateHeight(); }, value: value, defaultValue: defaultValue, }), valued = _s.valued, focused = _s.focused, onBlur = _s.onBlur, onFocus = _s.onFocus, onChange = _s.onChange; var _t = __read(useEnsuredRef(forwardedRef), 2), ref = _t[0], refHandler = _t[1]; // the container element adds some padding so that the content can scroll and // not be covered by the floating label. unfortunately, this means that the entire // container is no longer clickable to focus the input. This is used to add that // functionality back. var handleClick = useCallback(function (event) { if (ref.current && event.target === event.currentTarget) { ref.current.focus(); } }, [ref]); var area = (_jsx("textarea", __assign({}, props, { ref: refHandler, rows: rows, disabled: disabled, onFocus: onFocus, onBlur: onBlur, onChange: onChange, style: __assign(__assign({}, areaStyle), { height: height !== null && height !== void 0 ? height : areaStyle === null || areaStyle === void 0 ? void 0 : areaStyle.height }), className: cn(block({ scrollable: scrollable || resize === "none", floating: label && theme !== "none", rh: resize === "horizontal", rv: resize === "vertical", rn: resize === "auto" || resize === "none", }), areaClassName) }))); var children = area; if (resize === "auto") { children = (_jsxs("div", __assign({ style: { height: height }, className: container("inner", { animate: animate }) }, { children: [area, _jsx("textarea", { "aria-hidden": true, defaultValue: value || defaultValue, id: "".concat(id, "-mask"), ref: maskRefHandler, readOnly: true, rows: rows, tabIndex: -1, style: areaStyle, className: cn(block({ rn: true, mask: true, floating: label && theme !== "none", }), areaClassName) })] }))); } var inline = propInline; if (resize === "horizontal" || resize === "both") { // have to force it inline or else you won't be able to resize // it horizontally. inline = true; } return (_jsxs(TextFieldContainer, __assign({}, containerProps, { style: __assign(__assign({}, style), { height: height ? "calc(".concat(PADDING_VARIABLES, " + ").concat(height, "px)") : style === null || style === void 0 ? void 0 : style.height }), className: cn(container({ animate: animate && resize === "auto", cursor: !disabled, }), className), ref: containerRef, theme: theme, error: error, active: focused, label: !!label, dense: dense, inline: inline, stretch: stretch, disabled: disabled, isLeftAddon: isLeftAddon, isRightAddon: isRightAddon, leftChildren: leftChildren, rightChildren: rightChildren, underlineDirection: underlineDirection, onClick: !disabled ? handleClick : undefined }, { children: [_jsx(FloatingLabel, __assign({ style: labelStyle, className: labelClassName, htmlFor: id, error: error, active: focused, floating: focused || valued, valued: valued, dense: dense, disabled: disabled }, { children: label })), children] }))); }); //# sourceMappingURL=TextArea.js.map