UNPKG

@navikt/ds-react

Version:

React components from the Norwegian Labour and Welfare Administration.

221 lines 10.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx */ const react_1 = __importStar(require("react")); const react_dom_1 = __importDefault(require("react-dom")); const hooks_1 = require("../util/hooks"); const debounce_1 = __importDefault(require("./debounce")); const useMergeRefs_1 = require("./hooks/useMergeRefs"); const owner_1 = require("./owner"); const checkState = (prevState, newState, renders) => { const { outerHeightStyle, overflow } = newState; // Need a large enough difference to update the height. // This prevents infinite rendering loop. if (renders.current < 20 && ((outerHeightStyle > 0 && Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) || prevState.overflow !== overflow)) { renders.current += 1; return newState; } if (process.env.NODE_ENV !== "production" && renders.current === 20) { console.error("Textarea: Too many re-renders. The layout is unstable. TextareaAutosize limits the number of renders to prevent an infinite loop."); } return prevState; }; function getStyleValue(value) { return parseInt(value, 10) || 0; } const TextareaAutosize = (0, react_1.forwardRef)((_a, ref) => { var _b, _c; var { className, onChange, maxRows, minRows = 1, autoScrollbar, style, value } = _a, other = __rest(_a, ["className", "onChange", "maxRows", "minRows", "autoScrollbar", "style", "value"]); const { current: isControlled } = (0, react_1.useRef)(value != null); const inputRef = (0, react_1.useRef)(null); const handleRef = (0, useMergeRefs_1.useMergeRefs)(inputRef, ref); const shadowRef = (0, react_1.useRef)(null); const renders = (0, react_1.useRef)(0); const [state, setState] = (0, react_1.useState)({ outerHeightStyle: 0 }); const getUpdatedState = react_1.default.useCallback(() => { const input = inputRef.current; const containerWindow = (0, owner_1.ownerWindow)(input); const computedStyle = containerWindow.getComputedStyle(input); // If input's width is shrunk and it's not visible, don't sync height. if (computedStyle.width === "0px") { return { outerHeightStyle: 0 }; } const inputShallow = shadowRef.current; inputShallow.style.width = computedStyle.width; inputShallow.value = input.value || other.placeholder || "x"; if (inputShallow.value.slice(-1) === "\n") { // Certain fonts which overflow the line height will cause the textarea // to report a different scrollHeight depending on whether the last line // is empty. Make it non-empty to avoid this issue. inputShallow.value += " "; } const boxSizing = computedStyle.boxSizing; const padding = getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop); const border = getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth); // The height of the inner content const innerHeight = inputShallow.scrollHeight - padding; // Measure height of a textarea with a single row inputShallow.value = "x"; const singleRowHeight = inputShallow.scrollHeight - padding; // The height of the outer content let outerHeight = innerHeight; if (minRows) { outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight); } if (maxRows) { outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight); } outerHeight = Math.max(outerHeight, singleRowHeight); // Take the box sizing into account for applying this value as a style. const outerHeightStyle = outerHeight + (boxSizing === "border-box" ? padding + border : 0); const overflow = Math.abs(outerHeight - innerHeight) <= 1; return { outerHeightStyle, overflow }; }, [maxRows, minRows, other.placeholder]); const syncHeight = () => { const newState = getUpdatedState(); if (isEmpty(newState)) { return; } setState((prevState) => checkState(prevState, newState, renders)); }; (0, hooks_1.useClientLayoutEffect)(() => { const syncHeightWithFlushSync = () => { const newState = getUpdatedState(); if (isEmpty(newState)) { return; } // In React 18, state updates in a ResizeObserver's callback are happening after // the paint, this leads to an infinite rendering. // Using flushSync ensures that the state is updated before the next paint. // Related issue - https://github.com/facebook/react/issues/24331 react_dom_1.default.flushSync(() => { setState((prevState) => checkState(prevState, newState, renders)); }); }; const handleResize = (0, debounce_1.default)(() => { var _a, _b, _c; renders.current = 0; if (((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.style.height) || ((_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.style.width)) { // User has resized manually if (((_c = inputRef.current) === null || _c === void 0 ? void 0 : _c.style.overflow) === "hidden") { setState((oldState) => (Object.assign(Object.assign({}, oldState), { overflow: false }))); // The state update isn't important, we just need to trigger a rerender } return; } syncHeightWithFlushSync(); }, 166, true); const input = inputRef.current; const containerWindow = (0, owner_1.ownerWindow)(input); containerWindow.addEventListener("resize", handleResize); let resizeObserver; if (typeof ResizeObserver !== "undefined") { resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(input); } return () => { handleResize.clear(); containerWindow.removeEventListener("resize", handleResize); if (resizeObserver) { resizeObserver.disconnect(); } }; }, [getUpdatedState]); (0, hooks_1.useClientLayoutEffect)(() => { syncHeight(); }); // biome-ignore lint/correctness/useExhaustiveDependencies: Since value is an external prop, we want to reset the renders on every time it changes, even when it is undefined or empty. (0, react_1.useEffect)(() => { renders.current = 0; }, [value]); const handleChange = (event) => { renders.current = 0; if (!isControlled) { syncHeight(); } if (onChange) { onChange(event); } }; const mainStyle = Object.assign({ "--__ac-textarea-height": state.outerHeightStyle + "px", "--__axc-textarea-height": state.outerHeightStyle + "px", // Need a large enough difference to allow scrolling. // This prevents infinite rendering loop. overflow: state.overflow && !autoScrollbar && !((_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.style.height) && !((_c = inputRef.current) === null || _c === void 0 ? void 0 : _c.style.width) ? "hidden" : undefined }, style); return (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement("textarea", Object.assign({ value: value, onChange: handleChange, ref: handleRef, // Apply the rows prop to get a "correct" first SSR paint rows: minRows, style: mainStyle }, other, { className: className })), react_1.default.createElement("textarea", { "aria-hidden": true, className: className, readOnly: true, ref: shadowRef, tabIndex: -1, style: Object.assign({ // Visibility needed to hide the extra text area on iPads visibility: "hidden", // Remove from the content flow position: "absolute", // Ignore the scrollbar width overflow: "hidden", height: 0, top: 0, left: 0, // Create a new layer, increase the isolation of the computed values transform: "translateZ(0)" }, style) }))); }); function isEmpty(obj) { return (obj === undefined || obj === null || Object.keys(obj).length === 0 || (obj.outerHeightStyle === 0 && !obj.overflow)); } exports.default = TextareaAutosize; //# sourceMappingURL=TextareaAutoSize.js.map