@navikt/ds-react 5.10.4 → 5.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_docs.json +38 -0
- package/cjs/form/Textarea.js +4 -3
- package/cjs/form/TextareaCounter.js +45 -6
- package/cjs/util/TextareaAutoSize.js +67 -69
- package/esm/form/Textarea.d.ts +7 -1
- package/esm/form/Textarea.js +4 -3
- package/esm/form/Textarea.js.map +1 -1
- package/esm/form/TextareaCounter.js +22 -6
- package/esm/form/TextareaCounter.js.map +1 -1
- package/esm/util/TextareaAutoSize.d.ts +4 -0
- package/esm/util/TextareaAutoSize.js +67 -69
- package/esm/util/TextareaAutoSize.js.map +1 -1
- package/package.json +3 -3
- package/src/form/Textarea.tsx +12 -2
- package/src/form/TextareaCounter.tsx +39 -12
- package/src/form/stories/textarea.stories.tsx +38 -2
- package/src/util/TextareaAutoSize.tsx +99 -82
package/_docs.json
CHANGED
|
@@ -3507,6 +3507,25 @@
|
|
|
3507
3507
|
}
|
|
3508
3508
|
],
|
|
3509
3509
|
"required": false,
|
|
3510
|
+
"type": {
|
|
3511
|
+
"name": "boolean | \"vertical\" | \"horizontal\""
|
|
3512
|
+
}
|
|
3513
|
+
},
|
|
3514
|
+
"UNSAFE_autoScrollbar": {
|
|
3515
|
+
"defaultValue": null,
|
|
3516
|
+
"description": "Textarea will stop growing and get a scrollbar when there's no more room to grow.\nRequires `display:flex` on the parent.\nExperimental feature that may be removed or get breaking changes in a minor version.",
|
|
3517
|
+
"name": "UNSAFE_autoScrollbar",
|
|
3518
|
+
"parent": {
|
|
3519
|
+
"fileName": "src/form/Textarea.tsx",
|
|
3520
|
+
"name": "TextareaProps"
|
|
3521
|
+
},
|
|
3522
|
+
"declarations": [
|
|
3523
|
+
{
|
|
3524
|
+
"fileName": "src/form/Textarea.tsx",
|
|
3525
|
+
"name": "TextareaProps"
|
|
3526
|
+
}
|
|
3527
|
+
],
|
|
3528
|
+
"required": false,
|
|
3510
3529
|
"type": {
|
|
3511
3530
|
"name": "boolean"
|
|
3512
3531
|
}
|
|
@@ -10411,6 +10430,25 @@
|
|
|
10411
10430
|
"name": "number"
|
|
10412
10431
|
}
|
|
10413
10432
|
},
|
|
10433
|
+
"autoScrollbar": {
|
|
10434
|
+
"defaultValue": null,
|
|
10435
|
+
"description": "If true, textarea will never get `overflow:hidden`",
|
|
10436
|
+
"name": "autoScrollbar",
|
|
10437
|
+
"parent": {
|
|
10438
|
+
"fileName": "src/util/TextareaAutoSize.tsx",
|
|
10439
|
+
"name": "TextareaAutosizeProps"
|
|
10440
|
+
},
|
|
10441
|
+
"declarations": [
|
|
10442
|
+
{
|
|
10443
|
+
"fileName": "src/util/TextareaAutoSize.tsx",
|
|
10444
|
+
"name": "TextareaAutosizeProps"
|
|
10445
|
+
}
|
|
10446
|
+
],
|
|
10447
|
+
"required": false,
|
|
10448
|
+
"type": {
|
|
10449
|
+
"name": "boolean"
|
|
10450
|
+
}
|
|
10451
|
+
},
|
|
10414
10452
|
"className": {
|
|
10415
10453
|
"defaultValue": null,
|
|
10416
10454
|
"description": "",
|
package/cjs/form/Textarea.js
CHANGED
|
@@ -60,7 +60,7 @@ const useFormField_1 = require("./useFormField");
|
|
|
60
60
|
exports.Textarea = (0, react_1.forwardRef)((props, ref) => {
|
|
61
61
|
var _a, _b, _c;
|
|
62
62
|
const { inputProps, errorId, showErrorMsg, hasError, size, inputDescriptionId, } = (0, useFormField_1.useFormField)(props, "textarea");
|
|
63
|
-
const { label, className, description, maxLength, hideLabel = false, resize, i18n, readOnly } = props, rest = __rest(props, ["label", "className", "description", "maxLength", "hideLabel", "resize", "i18n", "readOnly"]);
|
|
63
|
+
const { label, className, description, maxLength, hideLabel = false, resize, UNSAFE_autoScrollbar, i18n, readOnly } = props, rest = __rest(props, ["label", "className", "description", "maxLength", "hideLabel", "resize", "UNSAFE_autoScrollbar", "i18n", "readOnly"]);
|
|
64
64
|
const maxLengthId = (0, util_1.useId)();
|
|
65
65
|
const hasMaxLength = maxLength !== undefined && maxLength > 0;
|
|
66
66
|
const [controlledValue, setControlledValue] = (0, react_1.useState)((_a = props === null || props === void 0 ? void 0 : props.defaultValue) !== null && _a !== void 0 ? _a : "");
|
|
@@ -79,7 +79,8 @@ exports.Textarea = (0, react_1.forwardRef)((props, ref) => {
|
|
|
79
79
|
"navds-form-field--readonly": readOnly,
|
|
80
80
|
"navds-textarea--readonly": readOnly,
|
|
81
81
|
"navds-textarea--error": hasError,
|
|
82
|
-
"navds-textarea--
|
|
82
|
+
"navds-textarea--autoscrollbar": UNSAFE_autoScrollbar,
|
|
83
|
+
[`navds-textarea--resize-${resize === true ? "both" : resize}`]: resize,
|
|
83
84
|
}) },
|
|
84
85
|
react_1.default.createElement(typography_1.Label, { htmlFor: inputProps.id, size: size, className: (0, clsx_1.default)("navds-form-field__label", {
|
|
85
86
|
"navds-sr-only": hideLabel,
|
|
@@ -92,7 +93,7 @@ exports.Textarea = (0, react_1.forwardRef)((props, ref) => {
|
|
|
92
93
|
react_1.default.createElement("div", { className: "navds-textarea__wrapper" },
|
|
93
94
|
react_1.default.createElement(TextareaAutoSize_1.default, Object.assign({}, (0, util_1.omit)(rest, ["error", "errorId", "size"]), inputProps, { onChange: (e) => props.onChange
|
|
94
95
|
? props.onChange(e)
|
|
95
|
-
: setControlledValue(e.target.value), minRows: getMinRows(), ref: ref, readOnly: readOnly, className: (0, clsx_1.default)("navds-textarea__input", "navds-body-short", `navds-body-short--${size !== null && size !== void 0 ? size : "medium"}`) }, (describedBy ? { "aria-describedby": describedBy } : {}))),
|
|
96
|
+
: setControlledValue(e.target.value), minRows: getMinRows(), autoScrollbar: UNSAFE_autoScrollbar, ref: ref, readOnly: readOnly, className: (0, clsx_1.default)("navds-textarea__input", "navds-body-short", `navds-body-short--${size !== null && size !== void 0 ? size : "medium"}`) }, (describedBy ? { "aria-describedby": describedBy } : {}))),
|
|
96
97
|
hasMaxLength && !readOnly && !inputProps.disabled && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
97
98
|
react_1.default.createElement("span", { id: maxLengthId, className: "navds-sr-only" }, `Tekstområde med plass til ${maxLength} tegn.`),
|
|
98
99
|
react_1.default.createElement(TextareaCounter_1.default, { maxLength: maxLength, currentLength: (_c = (_b = props.value) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : controlledValue === null || controlledValue === void 0 ? void 0 : controlledValue.length, size: size, i18n: i18n })))),
|
|
@@ -1,18 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
5
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
29
|
const clsx_1 = __importDefault(require("clsx"));
|
|
7
|
-
const react_1 =
|
|
30
|
+
const react_1 = __importStar(require("react"));
|
|
8
31
|
const typography_1 = require("../typography");
|
|
32
|
+
const debounce_1 = __importDefault(require("../util/debounce"));
|
|
9
33
|
const TextareaCounter = ({ maxLength, currentLength, size, i18n }) => {
|
|
10
|
-
var _a, _b;
|
|
11
34
|
const difference = maxLength - currentLength;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
35
|
+
const [debouncedDiff, setDebouncedDiff] = (0, react_1.useState)(difference);
|
|
36
|
+
(0, react_1.useEffect)(() => {
|
|
37
|
+
const debounceFunc = (0, debounce_1.default)(() => {
|
|
38
|
+
setDebouncedDiff(difference);
|
|
39
|
+
}, 2000);
|
|
40
|
+
debounceFunc();
|
|
41
|
+
return () => {
|
|
42
|
+
debounceFunc.clear();
|
|
43
|
+
};
|
|
44
|
+
}, [difference]);
|
|
45
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
46
|
+
difference < 20 && (react_1.default.createElement("span", { role: "status", className: "navds-textarea__sr-counter navds-sr-only" }, getCounterText(debouncedDiff, i18n))),
|
|
47
|
+
react_1.default.createElement(typography_1.BodyShort, { className: (0, clsx_1.default)("navds-textarea__counter", {
|
|
48
|
+
"navds-textarea__counter--error": difference < 0,
|
|
49
|
+
}), size: size }, getCounterText(difference, i18n))));
|
|
50
|
+
};
|
|
51
|
+
const getCounterText = (difference, i18n) => {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
return difference < 0
|
|
15
54
|
? `${Math.abs(difference)} ${(_a = i18n === null || i18n === void 0 ? void 0 : i18n.counterTooMuch) !== null && _a !== void 0 ? _a : "tegn for mye"}`
|
|
16
|
-
: `${difference} ${(_b = i18n === null || i18n === void 0 ? void 0 : i18n.counterLeft) !== null && _b !== void 0 ? _b : "tegn igjen"}
|
|
55
|
+
: `${difference} ${(_b = i18n === null || i18n === void 0 ? void 0 : i18n.counterLeft) !== null && _b !== void 0 ? _b : "tegn igjen"}`;
|
|
17
56
|
};
|
|
18
57
|
exports.default = TextareaCounter;
|
|
@@ -37,53 +37,75 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.
|
|
40
|
+
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx */
|
|
41
41
|
const react_1 = __importStar(require("react"));
|
|
42
42
|
const react_dom_1 = __importDefault(require("react-dom"));
|
|
43
43
|
const util_1 = require("../util");
|
|
44
|
+
const updateState = (prevState, newState, renders) => {
|
|
45
|
+
const { outerHeightStyle, overflow } = newState;
|
|
46
|
+
// Need a large enough difference to update the height.
|
|
47
|
+
// This prevents infinite rendering loop.
|
|
48
|
+
if (renders.current < 20 &&
|
|
49
|
+
((outerHeightStyle > 0 &&
|
|
50
|
+
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
51
|
+
prevState.overflow !== overflow)) {
|
|
52
|
+
renders.current += 1;
|
|
53
|
+
return {
|
|
54
|
+
overflow,
|
|
55
|
+
outerHeightStyle,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (process.env.NODE_ENV !== "production") {
|
|
59
|
+
if (renders.current === 20) {
|
|
60
|
+
console.error([
|
|
61
|
+
"Textarea: Too many re-renders. The layout is unstable.",
|
|
62
|
+
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
63
|
+
].join("\n"));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return prevState;
|
|
67
|
+
};
|
|
44
68
|
/**
|
|
45
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument.ts
|
|
46
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow.ts
|
|
69
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument/ownerDocument.ts
|
|
70
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow/ownerWindow.ts
|
|
47
71
|
*/
|
|
48
72
|
const ownerWindow = (node) => {
|
|
49
73
|
const doc = (node && node.ownerDocument) || document;
|
|
50
74
|
return doc.defaultView || window;
|
|
51
75
|
};
|
|
52
|
-
function getStyleValue(
|
|
53
|
-
return parseInt(
|
|
76
|
+
function getStyleValue(value) {
|
|
77
|
+
return parseInt(value, 10) || 0;
|
|
54
78
|
}
|
|
55
79
|
const TextareaAutosize = (0, react_1.forwardRef)((_a, ref) => {
|
|
56
|
-
var { className, onChange, maxRows, minRows = 1, style, value } = _a, other = __rest(_a, ["className", "onChange", "maxRows", "minRows", "style", "value"]);
|
|
80
|
+
var { className, onChange, maxRows, minRows = 1, autoScrollbar, style, value } = _a, other = __rest(_a, ["className", "onChange", "maxRows", "minRows", "autoScrollbar", "style", "value"]);
|
|
57
81
|
const { current: isControlled } = (0, react_1.useRef)(value != null);
|
|
58
82
|
const inputRef = (0, react_1.useRef)(null);
|
|
59
83
|
const handleRef = (0, react_1.useMemo)(() => (0, util_1.mergeRefs)([inputRef, ref]), [ref]);
|
|
60
84
|
const shadowRef = (0, react_1.useRef)(null);
|
|
61
85
|
const renders = (0, react_1.useRef)(0);
|
|
62
|
-
const [state, setState] = (0, react_1.useState)({});
|
|
86
|
+
const [state, setState] = (0, react_1.useState)({ outerHeightStyle: 0 });
|
|
63
87
|
const getUpdatedState = react_1.default.useCallback(() => {
|
|
64
|
-
if (!inputRef.current || !shadowRef.current)
|
|
65
|
-
return;
|
|
66
88
|
const input = inputRef.current;
|
|
67
89
|
const containerWindow = ownerWindow(input);
|
|
68
90
|
const computedStyle = containerWindow.getComputedStyle(input);
|
|
69
91
|
// If input's width is shrunk and it's not visible, don't sync height.
|
|
70
92
|
if (computedStyle.width === "0px") {
|
|
71
|
-
return;
|
|
93
|
+
return { outerHeightStyle: 0 };
|
|
72
94
|
}
|
|
73
95
|
const inputShallow = shadowRef.current;
|
|
74
96
|
inputShallow.style.width = computedStyle.width;
|
|
75
|
-
inputShallow.value = input.value ||
|
|
97
|
+
inputShallow.value = input.value || other.placeholder || "x";
|
|
76
98
|
if (inputShallow.value.slice(-1) === "\n") {
|
|
77
99
|
// Certain fonts which overflow the line height will cause the textarea
|
|
78
100
|
// to report a different scrollHeight depending on whether the last line
|
|
79
101
|
// is empty. Make it non-empty to avoid this issue.
|
|
80
102
|
inputShallow.value += " ";
|
|
81
103
|
}
|
|
82
|
-
const boxSizing = computedStyle
|
|
83
|
-
const padding = getStyleValue(computedStyle
|
|
84
|
-
getStyleValue(computedStyle
|
|
85
|
-
const border = getStyleValue(computedStyle
|
|
86
|
-
getStyleValue(computedStyle
|
|
104
|
+
const boxSizing = computedStyle.boxSizing;
|
|
105
|
+
const padding = getStyleValue(computedStyle.paddingBottom) +
|
|
106
|
+
getStyleValue(computedStyle.paddingTop);
|
|
107
|
+
const border = getStyleValue(computedStyle.borderBottomWidth) +
|
|
108
|
+
getStyleValue(computedStyle.borderTopWidth);
|
|
87
109
|
// The height of the inner content
|
|
88
110
|
const innerHeight = inputShallow.scrollHeight - padding;
|
|
89
111
|
// Measure height of a textarea with a single row
|
|
@@ -102,77 +124,50 @@ const TextareaAutosize = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
102
124
|
const outerHeightStyle = outerHeight + (boxSizing === "border-box" ? padding + border : 0);
|
|
103
125
|
const overflow = Math.abs(outerHeight - innerHeight) <= 1;
|
|
104
126
|
return { outerHeightStyle, overflow };
|
|
105
|
-
}, [maxRows, minRows, other
|
|
127
|
+
}, [maxRows, minRows, other.placeholder]);
|
|
106
128
|
const syncHeight = react_1.default.useCallback(() => {
|
|
107
129
|
const newState = getUpdatedState();
|
|
108
130
|
if (isEmpty(newState)) {
|
|
109
131
|
return;
|
|
110
132
|
}
|
|
111
|
-
setState((prevState) =>
|
|
112
|
-
return updateState(prevState, newState);
|
|
113
|
-
});
|
|
133
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
114
134
|
}, [getUpdatedState]);
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
((outerHeightStyle > 0 &&
|
|
121
|
-
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
122
|
-
prevState.overflow !== overflow)) {
|
|
123
|
-
renders.current += 1;
|
|
124
|
-
return {
|
|
125
|
-
overflow,
|
|
126
|
-
outerHeightStyle,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
if (process.env.NODE_ENV !== "production") {
|
|
130
|
-
if (renders.current === 20) {
|
|
131
|
-
console.error([
|
|
132
|
-
"Textarea: Too many re-renders. The layout is unstable.",
|
|
133
|
-
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
134
|
-
].join("\n"));
|
|
135
|
+
(0, util_1.useClientLayoutEffect)(() => {
|
|
136
|
+
const syncHeightWithFlushSync = () => {
|
|
137
|
+
const newState = getUpdatedState();
|
|
138
|
+
if (isEmpty(newState)) {
|
|
139
|
+
return;
|
|
135
140
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
// In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
|
|
145
|
-
// when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
|
|
146
|
-
// Related issue - https://github.com/facebook/react/issues/24331
|
|
147
|
-
react_dom_1.default.flushSync(() => {
|
|
148
|
-
setState((prevState) => {
|
|
149
|
-
return updateState(prevState, newState);
|
|
141
|
+
// In React 18, state updates in a ResizeObserver's callback are happening after
|
|
142
|
+
// the paint, this leads to an infinite rendering.
|
|
143
|
+
//
|
|
144
|
+
// Using flushSync ensures that the states is updated before the next pain.
|
|
145
|
+
// Related issue - https://github.com/facebook/react/issues/24331
|
|
146
|
+
react_dom_1.default.flushSync(() => {
|
|
147
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
150
148
|
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
react_1.default.useEffect(() => {
|
|
154
|
-
const handleResize = (0, util_1.debounce)(() => {
|
|
149
|
+
};
|
|
150
|
+
const handleResize = () => {
|
|
155
151
|
renders.current = 0;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
});
|
|
160
|
-
let resizeObserver;
|
|
152
|
+
syncHeightWithFlushSync();
|
|
153
|
+
};
|
|
154
|
+
const debounceHandleResize = (0, util_1.debounce)(handleResize);
|
|
161
155
|
const input = inputRef.current;
|
|
162
156
|
const containerWindow = ownerWindow(input);
|
|
163
|
-
containerWindow.addEventListener("resize",
|
|
157
|
+
containerWindow.addEventListener("resize", debounceHandleResize);
|
|
158
|
+
let resizeObserver;
|
|
164
159
|
if (typeof ResizeObserver !== "undefined") {
|
|
165
160
|
resizeObserver = new ResizeObserver(handleResize);
|
|
166
161
|
resizeObserver.observe(input);
|
|
167
162
|
}
|
|
168
163
|
return () => {
|
|
169
|
-
|
|
170
|
-
containerWindow.removeEventListener("resize",
|
|
164
|
+
debounceHandleResize.clear();
|
|
165
|
+
containerWindow.removeEventListener("resize", debounceHandleResize);
|
|
171
166
|
if (resizeObserver) {
|
|
172
167
|
resizeObserver.disconnect();
|
|
173
168
|
}
|
|
174
169
|
};
|
|
175
|
-
});
|
|
170
|
+
}, [getUpdatedState]);
|
|
176
171
|
(0, util_1.useClientLayoutEffect)(() => {
|
|
177
172
|
syncHeight();
|
|
178
173
|
});
|
|
@@ -191,7 +186,10 @@ const TextareaAutosize = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
191
186
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
192
187
|
react_1.default.createElement("textarea", Object.assign({ value: value, onChange: handleChange, ref: handleRef,
|
|
193
188
|
// Apply the rows prop to get a "correct" first SSR paint
|
|
194
|
-
rows: minRows, style: Object.assign(
|
|
189
|
+
rows: minRows, style: Object.assign({ height: state.outerHeightStyle,
|
|
190
|
+
// Need a large enough difference to allow scrolling.
|
|
191
|
+
// This prevents infinite rendering loop.
|
|
192
|
+
overflow: state.overflow && !autoScrollbar ? "hidden" : undefined }, style) }, other, { className: className })),
|
|
195
193
|
react_1.default.createElement("textarea", { "aria-hidden": true, className: className, readOnly: true, ref: shadowRef, tabIndex: -1, style: Object.assign({
|
|
196
194
|
// Visibility needed to hide the extra text area on iPads
|
|
197
195
|
visibility: "hidden",
|
|
@@ -206,6 +204,6 @@ function isEmpty(obj) {
|
|
|
206
204
|
return (obj === undefined ||
|
|
207
205
|
obj === null ||
|
|
208
206
|
Object.keys(obj).length === 0 ||
|
|
209
|
-
(
|
|
207
|
+
(obj.outerHeightStyle === 0 && !obj.overflow));
|
|
210
208
|
}
|
|
211
209
|
exports.default = TextareaAutosize;
|
package/esm/form/Textarea.d.ts
CHANGED
|
@@ -36,7 +36,13 @@ export interface TextareaProps extends FormFieldProps, React.TextareaHTMLAttribu
|
|
|
36
36
|
/**
|
|
37
37
|
* Enables resizing of field
|
|
38
38
|
*/
|
|
39
|
-
resize?: boolean;
|
|
39
|
+
resize?: boolean | "vertical" | "horizontal";
|
|
40
|
+
/**
|
|
41
|
+
* Textarea will stop growing and get a scrollbar when there's no more room to grow.
|
|
42
|
+
* Requires `display:flex` on the parent.
|
|
43
|
+
* Experimental feature that may be removed or get breaking changes in a minor version.
|
|
44
|
+
*/
|
|
45
|
+
UNSAFE_autoScrollbar?: boolean;
|
|
40
46
|
/**
|
|
41
47
|
* i18n-translations for counter-text
|
|
42
48
|
*/
|
package/esm/form/Textarea.js
CHANGED
|
@@ -31,7 +31,7 @@ import { useFormField } from "./useFormField";
|
|
|
31
31
|
export const Textarea = forwardRef((props, ref) => {
|
|
32
32
|
var _a, _b, _c;
|
|
33
33
|
const { inputProps, errorId, showErrorMsg, hasError, size, inputDescriptionId, } = useFormField(props, "textarea");
|
|
34
|
-
const { label, className, description, maxLength, hideLabel = false, resize, i18n, readOnly } = props, rest = __rest(props, ["label", "className", "description", "maxLength", "hideLabel", "resize", "i18n", "readOnly"]);
|
|
34
|
+
const { label, className, description, maxLength, hideLabel = false, resize, UNSAFE_autoScrollbar, i18n, readOnly } = props, rest = __rest(props, ["label", "className", "description", "maxLength", "hideLabel", "resize", "UNSAFE_autoScrollbar", "i18n", "readOnly"]);
|
|
35
35
|
const maxLengthId = useId();
|
|
36
36
|
const hasMaxLength = maxLength !== undefined && maxLength > 0;
|
|
37
37
|
const [controlledValue, setControlledValue] = useState((_a = props === null || props === void 0 ? void 0 : props.defaultValue) !== null && _a !== void 0 ? _a : "");
|
|
@@ -50,7 +50,8 @@ export const Textarea = forwardRef((props, ref) => {
|
|
|
50
50
|
"navds-form-field--readonly": readOnly,
|
|
51
51
|
"navds-textarea--readonly": readOnly,
|
|
52
52
|
"navds-textarea--error": hasError,
|
|
53
|
-
"navds-textarea--
|
|
53
|
+
"navds-textarea--autoscrollbar": UNSAFE_autoScrollbar,
|
|
54
|
+
[`navds-textarea--resize-${resize === true ? "both" : resize}`]: resize,
|
|
54
55
|
}) },
|
|
55
56
|
React.createElement(Label, { htmlFor: inputProps.id, size: size, className: cl("navds-form-field__label", {
|
|
56
57
|
"navds-sr-only": hideLabel,
|
|
@@ -63,7 +64,7 @@ export const Textarea = forwardRef((props, ref) => {
|
|
|
63
64
|
React.createElement("div", { className: "navds-textarea__wrapper" },
|
|
64
65
|
React.createElement(TextareaAutosize, Object.assign({}, omit(rest, ["error", "errorId", "size"]), inputProps, { onChange: (e) => props.onChange
|
|
65
66
|
? props.onChange(e)
|
|
66
|
-
: setControlledValue(e.target.value), minRows: getMinRows(), ref: ref, readOnly: readOnly, className: cl("navds-textarea__input", "navds-body-short", `navds-body-short--${size !== null && size !== void 0 ? size : "medium"}`) }, (describedBy ? { "aria-describedby": describedBy } : {}))),
|
|
67
|
+
: setControlledValue(e.target.value), minRows: getMinRows(), autoScrollbar: UNSAFE_autoScrollbar, ref: ref, readOnly: readOnly, className: cl("navds-textarea__input", "navds-body-short", `navds-body-short--${size !== null && size !== void 0 ? size : "medium"}`) }, (describedBy ? { "aria-describedby": describedBy } : {}))),
|
|
67
68
|
hasMaxLength && !readOnly && !inputProps.disabled && (React.createElement(React.Fragment, null,
|
|
68
69
|
React.createElement("span", { id: maxLengthId, className: "navds-sr-only" }, `Tekstområde med plass til ${maxLength} tegn.`),
|
|
69
70
|
React.createElement(Counter, { maxLength: maxLength, currentLength: (_c = (_b = props.value) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : controlledValue === null || controlledValue === void 0 ? void 0 : controlledValue.length, size: size, i18n: i18n })))),
|
package/esm/form/Textarea.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Textarea.js","sourceRoot":"","sources":["../../src/form/Textarea.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,EAAE,MAAM,MAAM,CAAC;AACtB,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,gBAAgB,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,OAAO,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAkB,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"Textarea.js","sourceRoot":"","sources":["../../src/form/Textarea.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import cl from "clsx";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
3
|
import { BodyShort } from "../typography";
|
|
4
|
+
import debounce from "../util/debounce";
|
|
4
5
|
const TextareaCounter = ({ maxLength, currentLength, size, i18n }) => {
|
|
5
|
-
var _a, _b;
|
|
6
6
|
const difference = maxLength - currentLength;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const [debouncedDiff, setDebouncedDiff] = useState(difference);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const debounceFunc = debounce(() => {
|
|
10
|
+
setDebouncedDiff(difference);
|
|
11
|
+
}, 2000);
|
|
12
|
+
debounceFunc();
|
|
13
|
+
return () => {
|
|
14
|
+
debounceFunc.clear();
|
|
15
|
+
};
|
|
16
|
+
}, [difference]);
|
|
17
|
+
return (React.createElement(React.Fragment, null,
|
|
18
|
+
difference < 20 && (React.createElement("span", { role: "status", className: "navds-textarea__sr-counter navds-sr-only" }, getCounterText(debouncedDiff, i18n))),
|
|
19
|
+
React.createElement(BodyShort, { className: cl("navds-textarea__counter", {
|
|
20
|
+
"navds-textarea__counter--error": difference < 0,
|
|
21
|
+
}), size: size }, getCounterText(difference, i18n))));
|
|
22
|
+
};
|
|
23
|
+
const getCounterText = (difference, i18n) => {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
return difference < 0
|
|
10
26
|
? `${Math.abs(difference)} ${(_a = i18n === null || i18n === void 0 ? void 0 : i18n.counterTooMuch) !== null && _a !== void 0 ? _a : "tegn for mye"}`
|
|
11
|
-
: `${difference} ${(_b = i18n === null || i18n === void 0 ? void 0 : i18n.counterLeft) !== null && _b !== void 0 ? _b : "tegn igjen"}
|
|
27
|
+
: `${difference} ${(_b = i18n === null || i18n === void 0 ? void 0 : i18n.counterLeft) !== null && _b !== void 0 ? _b : "tegn igjen"}`;
|
|
12
28
|
};
|
|
13
29
|
export default TextareaCounter;
|
|
14
30
|
//# sourceMappingURL=TextareaCounter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextareaCounter.js","sourceRoot":"","sources":["../../src/form/TextareaCounter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,MAAM,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"TextareaCounter.js","sourceRoot":"","sources":["../../src/form/TextareaCounter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,MAAM,CAAC;AACtB,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,QAAQ,MAAM,kBAAkB,CAAC;AAUxC,MAAM,eAAe,GAAG,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAS,EAAE,EAAE;IAC1E,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;IAE7C,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE/D,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,EAAE;YACjC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC,EAAE,IAAI,CAAC,CAAC;QACT,YAAY,EAAE,CAAC;QAEf,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,OAAO,CACL;QACG,UAAU,GAAG,EAAE,IAAI,CAClB,8BACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,0CAA0C,IAEnD,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,CAC/B,CACR;QAED,oBAAC,SAAS,IACR,SAAS,EAAE,EAAE,CAAC,yBAAyB,EAAE;gBACvC,gCAAgC,EAAE,UAAU,GAAG,CAAC;aACjD,CAAC,EACF,IAAI,EAAE,IAAI,IAET,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,CACvB,CACX,CACJ,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,UAAkB,EAAE,IAA2B,EAAE,EAAE;;IACzE,OAAA,UAAU,GAAG,CAAC;QACZ,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,cAAc,mCAAI,cAAc,EAAE;QACrE,CAAC,CAAC,GAAG,UAAU,IAAI,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,WAAW,mCAAI,YAAY,EAAE,CAAA;CAAA,CAAC;AAE3D,eAAe,eAAe,CAAC"}
|
|
@@ -9,6 +9,10 @@ interface TextareaAutosizeProps extends Omit<React.TextareaHTMLAttributes<HTMLTe
|
|
|
9
9
|
* @default 1
|
|
10
10
|
*/
|
|
11
11
|
minRows?: number;
|
|
12
|
+
/**
|
|
13
|
+
* If true, textarea will never get `overflow:hidden`
|
|
14
|
+
*/
|
|
15
|
+
autoScrollbar?: boolean;
|
|
12
16
|
}
|
|
13
17
|
declare const TextareaAutosize: React.ForwardRefExoticComponent<TextareaAutosizeProps & React.RefAttributes<HTMLTextAreaElement>>;
|
|
14
18
|
export default TextareaAutosize;
|
|
@@ -9,53 +9,75 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.
|
|
12
|
+
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx */
|
|
13
13
|
import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
|
14
14
|
import ReactDOM from "react-dom";
|
|
15
15
|
import { debounce, mergeRefs, useClientLayoutEffect } from "../util";
|
|
16
|
+
const updateState = (prevState, newState, renders) => {
|
|
17
|
+
const { outerHeightStyle, overflow } = newState;
|
|
18
|
+
// Need a large enough difference to update the height.
|
|
19
|
+
// This prevents infinite rendering loop.
|
|
20
|
+
if (renders.current < 20 &&
|
|
21
|
+
((outerHeightStyle > 0 &&
|
|
22
|
+
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
23
|
+
prevState.overflow !== overflow)) {
|
|
24
|
+
renders.current += 1;
|
|
25
|
+
return {
|
|
26
|
+
overflow,
|
|
27
|
+
outerHeightStyle,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (process.env.NODE_ENV !== "production") {
|
|
31
|
+
if (renders.current === 20) {
|
|
32
|
+
console.error([
|
|
33
|
+
"Textarea: Too many re-renders. The layout is unstable.",
|
|
34
|
+
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
35
|
+
].join("\n"));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return prevState;
|
|
39
|
+
};
|
|
16
40
|
/**
|
|
17
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument.ts
|
|
18
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow.ts
|
|
41
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument/ownerDocument.ts
|
|
42
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow/ownerWindow.ts
|
|
19
43
|
*/
|
|
20
44
|
const ownerWindow = (node) => {
|
|
21
45
|
const doc = (node && node.ownerDocument) || document;
|
|
22
46
|
return doc.defaultView || window;
|
|
23
47
|
};
|
|
24
|
-
function getStyleValue(
|
|
25
|
-
return parseInt(
|
|
48
|
+
function getStyleValue(value) {
|
|
49
|
+
return parseInt(value, 10) || 0;
|
|
26
50
|
}
|
|
27
51
|
const TextareaAutosize = forwardRef((_a, ref) => {
|
|
28
|
-
var { className, onChange, maxRows, minRows = 1, style, value } = _a, other = __rest(_a, ["className", "onChange", "maxRows", "minRows", "style", "value"]);
|
|
52
|
+
var { className, onChange, maxRows, minRows = 1, autoScrollbar, style, value } = _a, other = __rest(_a, ["className", "onChange", "maxRows", "minRows", "autoScrollbar", "style", "value"]);
|
|
29
53
|
const { current: isControlled } = useRef(value != null);
|
|
30
54
|
const inputRef = useRef(null);
|
|
31
55
|
const handleRef = useMemo(() => mergeRefs([inputRef, ref]), [ref]);
|
|
32
56
|
const shadowRef = useRef(null);
|
|
33
57
|
const renders = useRef(0);
|
|
34
|
-
const [state, setState] = useState({});
|
|
58
|
+
const [state, setState] = useState({ outerHeightStyle: 0 });
|
|
35
59
|
const getUpdatedState = React.useCallback(() => {
|
|
36
|
-
if (!inputRef.current || !shadowRef.current)
|
|
37
|
-
return;
|
|
38
60
|
const input = inputRef.current;
|
|
39
61
|
const containerWindow = ownerWindow(input);
|
|
40
62
|
const computedStyle = containerWindow.getComputedStyle(input);
|
|
41
63
|
// If input's width is shrunk and it's not visible, don't sync height.
|
|
42
64
|
if (computedStyle.width === "0px") {
|
|
43
|
-
return;
|
|
65
|
+
return { outerHeightStyle: 0 };
|
|
44
66
|
}
|
|
45
67
|
const inputShallow = shadowRef.current;
|
|
46
68
|
inputShallow.style.width = computedStyle.width;
|
|
47
|
-
inputShallow.value = input.value ||
|
|
69
|
+
inputShallow.value = input.value || other.placeholder || "x";
|
|
48
70
|
if (inputShallow.value.slice(-1) === "\n") {
|
|
49
71
|
// Certain fonts which overflow the line height will cause the textarea
|
|
50
72
|
// to report a different scrollHeight depending on whether the last line
|
|
51
73
|
// is empty. Make it non-empty to avoid this issue.
|
|
52
74
|
inputShallow.value += " ";
|
|
53
75
|
}
|
|
54
|
-
const boxSizing = computedStyle
|
|
55
|
-
const padding = getStyleValue(computedStyle
|
|
56
|
-
getStyleValue(computedStyle
|
|
57
|
-
const border = getStyleValue(computedStyle
|
|
58
|
-
getStyleValue(computedStyle
|
|
76
|
+
const boxSizing = computedStyle.boxSizing;
|
|
77
|
+
const padding = getStyleValue(computedStyle.paddingBottom) +
|
|
78
|
+
getStyleValue(computedStyle.paddingTop);
|
|
79
|
+
const border = getStyleValue(computedStyle.borderBottomWidth) +
|
|
80
|
+
getStyleValue(computedStyle.borderTopWidth);
|
|
59
81
|
// The height of the inner content
|
|
60
82
|
const innerHeight = inputShallow.scrollHeight - padding;
|
|
61
83
|
// Measure height of a textarea with a single row
|
|
@@ -74,77 +96,50 @@ const TextareaAutosize = forwardRef((_a, ref) => {
|
|
|
74
96
|
const outerHeightStyle = outerHeight + (boxSizing === "border-box" ? padding + border : 0);
|
|
75
97
|
const overflow = Math.abs(outerHeight - innerHeight) <= 1;
|
|
76
98
|
return { outerHeightStyle, overflow };
|
|
77
|
-
}, [maxRows, minRows, other
|
|
99
|
+
}, [maxRows, minRows, other.placeholder]);
|
|
78
100
|
const syncHeight = React.useCallback(() => {
|
|
79
101
|
const newState = getUpdatedState();
|
|
80
102
|
if (isEmpty(newState)) {
|
|
81
103
|
return;
|
|
82
104
|
}
|
|
83
|
-
setState((prevState) =>
|
|
84
|
-
return updateState(prevState, newState);
|
|
85
|
-
});
|
|
105
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
86
106
|
}, [getUpdatedState]);
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
((outerHeightStyle > 0 &&
|
|
93
|
-
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
94
|
-
prevState.overflow !== overflow)) {
|
|
95
|
-
renders.current += 1;
|
|
96
|
-
return {
|
|
97
|
-
overflow,
|
|
98
|
-
outerHeightStyle,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
if (process.env.NODE_ENV !== "production") {
|
|
102
|
-
if (renders.current === 20) {
|
|
103
|
-
console.error([
|
|
104
|
-
"Textarea: Too many re-renders. The layout is unstable.",
|
|
105
|
-
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
106
|
-
].join("\n"));
|
|
107
|
+
useClientLayoutEffect(() => {
|
|
108
|
+
const syncHeightWithFlushSync = () => {
|
|
109
|
+
const newState = getUpdatedState();
|
|
110
|
+
if (isEmpty(newState)) {
|
|
111
|
+
return;
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
// In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
|
|
117
|
-
// when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
|
|
118
|
-
// Related issue - https://github.com/facebook/react/issues/24331
|
|
119
|
-
ReactDOM.flushSync(() => {
|
|
120
|
-
setState((prevState) => {
|
|
121
|
-
return updateState(prevState, newState);
|
|
113
|
+
// In React 18, state updates in a ResizeObserver's callback are happening after
|
|
114
|
+
// the paint, this leads to an infinite rendering.
|
|
115
|
+
//
|
|
116
|
+
// Using flushSync ensures that the states is updated before the next pain.
|
|
117
|
+
// Related issue - https://github.com/facebook/react/issues/24331
|
|
118
|
+
ReactDOM.flushSync(() => {
|
|
119
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
122
120
|
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
React.useEffect(() => {
|
|
126
|
-
const handleResize = debounce(() => {
|
|
121
|
+
};
|
|
122
|
+
const handleResize = () => {
|
|
127
123
|
renders.current = 0;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
132
|
-
let resizeObserver;
|
|
124
|
+
syncHeightWithFlushSync();
|
|
125
|
+
};
|
|
126
|
+
const debounceHandleResize = debounce(handleResize);
|
|
133
127
|
const input = inputRef.current;
|
|
134
128
|
const containerWindow = ownerWindow(input);
|
|
135
|
-
containerWindow.addEventListener("resize",
|
|
129
|
+
containerWindow.addEventListener("resize", debounceHandleResize);
|
|
130
|
+
let resizeObserver;
|
|
136
131
|
if (typeof ResizeObserver !== "undefined") {
|
|
137
132
|
resizeObserver = new ResizeObserver(handleResize);
|
|
138
133
|
resizeObserver.observe(input);
|
|
139
134
|
}
|
|
140
135
|
return () => {
|
|
141
|
-
|
|
142
|
-
containerWindow.removeEventListener("resize",
|
|
136
|
+
debounceHandleResize.clear();
|
|
137
|
+
containerWindow.removeEventListener("resize", debounceHandleResize);
|
|
143
138
|
if (resizeObserver) {
|
|
144
139
|
resizeObserver.disconnect();
|
|
145
140
|
}
|
|
146
141
|
};
|
|
147
|
-
});
|
|
142
|
+
}, [getUpdatedState]);
|
|
148
143
|
useClientLayoutEffect(() => {
|
|
149
144
|
syncHeight();
|
|
150
145
|
});
|
|
@@ -163,7 +158,10 @@ const TextareaAutosize = forwardRef((_a, ref) => {
|
|
|
163
158
|
return (React.createElement(React.Fragment, null,
|
|
164
159
|
React.createElement("textarea", Object.assign({ value: value, onChange: handleChange, ref: handleRef,
|
|
165
160
|
// Apply the rows prop to get a "correct" first SSR paint
|
|
166
|
-
rows: minRows, style: Object.assign(
|
|
161
|
+
rows: minRows, style: Object.assign({ height: state.outerHeightStyle,
|
|
162
|
+
// Need a large enough difference to allow scrolling.
|
|
163
|
+
// This prevents infinite rendering loop.
|
|
164
|
+
overflow: state.overflow && !autoScrollbar ? "hidden" : undefined }, style) }, other, { className: className })),
|
|
167
165
|
React.createElement("textarea", { "aria-hidden": true, className: className, readOnly: true, ref: shadowRef, tabIndex: -1, style: Object.assign({
|
|
168
166
|
// Visibility needed to hide the extra text area on iPads
|
|
169
167
|
visibility: "hidden",
|
|
@@ -178,7 +176,7 @@ function isEmpty(obj) {
|
|
|
178
176
|
return (obj === undefined ||
|
|
179
177
|
obj === null ||
|
|
180
178
|
Object.keys(obj).length === 0 ||
|
|
181
|
-
(
|
|
179
|
+
(obj.outerHeightStyle === 0 && !obj.overflow));
|
|
182
180
|
}
|
|
183
181
|
export default TextareaAutosize;
|
|
184
182
|
//# sourceMappingURL=TextareaAutoSize.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextareaAutoSize.js","sourceRoot":"","sources":["../../src/util/TextareaAutoSize.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA
|
|
1
|
+
{"version":3,"file":"TextareaAutoSize.js","sourceRoot":"","sources":["../../src/util/TextareaAutoSize.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navikt/ds-react",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.0",
|
|
4
4
|
"description": "Aksel react-components for NAV designsystem",
|
|
5
5
|
"author": "Aksel | NAV designsystem team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@floating-ui/react": "0.25.4",
|
|
41
|
-
"@navikt/aksel-icons": "^5.
|
|
42
|
-
"@navikt/ds-tokens": "^5.
|
|
41
|
+
"@navikt/aksel-icons": "^5.11.0",
|
|
42
|
+
"@navikt/ds-tokens": "^5.11.0",
|
|
43
43
|
"@radix-ui/react-tabs": "1.0.0",
|
|
44
44
|
"@radix-ui/react-toggle-group": "1.0.0",
|
|
45
45
|
"clsx": "^1.2.1",
|
package/src/form/Textarea.tsx
CHANGED
|
@@ -45,7 +45,13 @@ export interface TextareaProps
|
|
|
45
45
|
/**
|
|
46
46
|
* Enables resizing of field
|
|
47
47
|
*/
|
|
48
|
-
resize?: boolean;
|
|
48
|
+
resize?: boolean | "vertical" | "horizontal";
|
|
49
|
+
/**
|
|
50
|
+
* Textarea will stop growing and get a scrollbar when there's no more room to grow.
|
|
51
|
+
* Requires `display:flex` on the parent.
|
|
52
|
+
* Experimental feature that may be removed or get breaking changes in a minor version.
|
|
53
|
+
*/
|
|
54
|
+
UNSAFE_autoScrollbar?: boolean;
|
|
49
55
|
/**
|
|
50
56
|
* i18n-translations for counter-text
|
|
51
57
|
*/
|
|
@@ -86,6 +92,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
86
92
|
maxLength,
|
|
87
93
|
hideLabel = false,
|
|
88
94
|
resize,
|
|
95
|
+
UNSAFE_autoScrollbar,
|
|
89
96
|
i18n,
|
|
90
97
|
readOnly,
|
|
91
98
|
...rest
|
|
@@ -121,7 +128,9 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
121
128
|
"navds-form-field--readonly": readOnly,
|
|
122
129
|
"navds-textarea--readonly": readOnly,
|
|
123
130
|
"navds-textarea--error": hasError,
|
|
124
|
-
"navds-textarea--
|
|
131
|
+
"navds-textarea--autoscrollbar": UNSAFE_autoScrollbar,
|
|
132
|
+
[`navds-textarea--resize-${resize === true ? "both" : resize}`]:
|
|
133
|
+
resize,
|
|
125
134
|
}
|
|
126
135
|
)}
|
|
127
136
|
>
|
|
@@ -157,6 +166,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
157
166
|
: setControlledValue(e.target.value)
|
|
158
167
|
}
|
|
159
168
|
minRows={getMinRows()}
|
|
169
|
+
autoScrollbar={UNSAFE_autoScrollbar}
|
|
160
170
|
ref={ref}
|
|
161
171
|
readOnly={readOnly}
|
|
162
172
|
className={cl(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import cl from "clsx";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
3
|
import { BodyShort } from "../typography";
|
|
4
|
+
import debounce from "../util/debounce";
|
|
4
5
|
import type { TextareaProps } from "./Textarea";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
@@ -13,19 +14,45 @@ interface Props {
|
|
|
13
14
|
const TextareaCounter = ({ maxLength, currentLength, size, i18n }: Props) => {
|
|
14
15
|
const difference = maxLength - currentLength;
|
|
15
16
|
|
|
17
|
+
const [debouncedDiff, setDebouncedDiff] = useState(difference);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const debounceFunc = debounce(() => {
|
|
21
|
+
setDebouncedDiff(difference);
|
|
22
|
+
}, 2000);
|
|
23
|
+
debounceFunc();
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
debounceFunc.clear();
|
|
27
|
+
};
|
|
28
|
+
}, [difference]);
|
|
29
|
+
|
|
16
30
|
return (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
<>
|
|
32
|
+
{difference < 20 && (
|
|
33
|
+
<span
|
|
34
|
+
role="status"
|
|
35
|
+
className="navds-textarea__sr-counter navds-sr-only"
|
|
36
|
+
>
|
|
37
|
+
{getCounterText(debouncedDiff, i18n)}
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
|
|
41
|
+
<BodyShort
|
|
42
|
+
className={cl("navds-textarea__counter", {
|
|
43
|
+
"navds-textarea__counter--error": difference < 0,
|
|
44
|
+
})}
|
|
45
|
+
size={size}
|
|
46
|
+
>
|
|
47
|
+
{getCounterText(difference, i18n)}
|
|
48
|
+
</BodyShort>
|
|
49
|
+
</>
|
|
28
50
|
);
|
|
29
51
|
};
|
|
30
52
|
|
|
53
|
+
const getCounterText = (difference: number, i18n: TextareaProps["i18n"]) =>
|
|
54
|
+
difference < 0
|
|
55
|
+
? `${Math.abs(difference)} ${i18n?.counterTooMuch ?? "tegn for mye"}`
|
|
56
|
+
: `${difference} ${i18n?.counterLeft ?? "tegn igjen"}`;
|
|
57
|
+
|
|
31
58
|
export default TextareaCounter;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import { Button } from "../../button";
|
|
3
4
|
import { Textarea } from "../index";
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof Textarea> = {
|
|
@@ -16,10 +17,13 @@ export const Default: StoryObj<typeof Textarea> = {
|
|
|
16
17
|
args: {
|
|
17
18
|
maxLength: 0,
|
|
18
19
|
label: "Ipsum enim quis culpa",
|
|
19
|
-
resize: false,
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
argTypes: {
|
|
23
|
+
resize: {
|
|
24
|
+
control: { type: "radio" },
|
|
25
|
+
options: [true, "vertical", "horizontal"],
|
|
26
|
+
},
|
|
23
27
|
size: {
|
|
24
28
|
control: { type: "radio" },
|
|
25
29
|
options: ["medium", "small"],
|
|
@@ -99,7 +103,7 @@ export const HideLabel = () => {
|
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
export const MaxLength = () => {
|
|
102
|
-
return <Textarea maxLength={
|
|
106
|
+
return <Textarea maxLength={50} label="Ipsum enim quis culpa" />;
|
|
103
107
|
};
|
|
104
108
|
|
|
105
109
|
export const MinRows = () => {
|
|
@@ -138,3 +142,35 @@ export const Readonly = () => {
|
|
|
138
142
|
</div>
|
|
139
143
|
);
|
|
140
144
|
};
|
|
145
|
+
|
|
146
|
+
export const AutoScrollbar = (props) => (
|
|
147
|
+
<div
|
|
148
|
+
style={{
|
|
149
|
+
border: "1px solid lightGreen",
|
|
150
|
+
height: "90vh",
|
|
151
|
+
display: "flex",
|
|
152
|
+
flexDirection: "column",
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<div style={{ border: "1px dashed gray" }}>
|
|
156
|
+
<h1>Header</h1>
|
|
157
|
+
</div>
|
|
158
|
+
<Textarea
|
|
159
|
+
resize
|
|
160
|
+
label="Textarea with autoScrollbar"
|
|
161
|
+
description="Description"
|
|
162
|
+
maxLength={30}
|
|
163
|
+
UNSAFE_autoScrollbar
|
|
164
|
+
{...props}
|
|
165
|
+
/>
|
|
166
|
+
<div style={{ border: "1px dashed gray" }}>
|
|
167
|
+
<Button>Send</Button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
AutoScrollbar.argTypes = {
|
|
172
|
+
error: { type: "string" },
|
|
173
|
+
hideLabel: { type: "boolean" },
|
|
174
|
+
maxRows: { type: "number" },
|
|
175
|
+
minRows: { type: "number" },
|
|
176
|
+
};
|
|
@@ -1,19 +1,57 @@
|
|
|
1
|
-
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.
|
|
1
|
+
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx */
|
|
2
2
|
import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import ReactDOM from "react-dom";
|
|
4
4
|
import { debounce, mergeRefs, useClientLayoutEffect } from "../util";
|
|
5
5
|
|
|
6
|
+
type State = {
|
|
7
|
+
outerHeightStyle: number;
|
|
8
|
+
overflow?: boolean | undefined;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const updateState = (
|
|
12
|
+
prevState: State,
|
|
13
|
+
newState: State,
|
|
14
|
+
renders: React.MutableRefObject<number>
|
|
15
|
+
) => {
|
|
16
|
+
const { outerHeightStyle, overflow } = newState;
|
|
17
|
+
// Need a large enough difference to update the height.
|
|
18
|
+
// This prevents infinite rendering loop.
|
|
19
|
+
if (
|
|
20
|
+
renders.current < 20 &&
|
|
21
|
+
((outerHeightStyle > 0 &&
|
|
22
|
+
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
23
|
+
prevState.overflow !== overflow)
|
|
24
|
+
) {
|
|
25
|
+
renders.current += 1;
|
|
26
|
+
return {
|
|
27
|
+
overflow,
|
|
28
|
+
outerHeightStyle,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (process.env.NODE_ENV !== "production") {
|
|
32
|
+
if (renders.current === 20) {
|
|
33
|
+
console.error(
|
|
34
|
+
[
|
|
35
|
+
"Textarea: Too many re-renders. The layout is unstable.",
|
|
36
|
+
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
37
|
+
].join("\n")
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return prevState;
|
|
42
|
+
};
|
|
43
|
+
|
|
6
44
|
/**
|
|
7
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument.ts
|
|
8
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow.ts
|
|
45
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument/ownerDocument.ts
|
|
46
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow/ownerWindow.ts
|
|
9
47
|
*/
|
|
10
|
-
const ownerWindow = (node: Node |
|
|
48
|
+
const ownerWindow = (node: Node | undefined): Window => {
|
|
11
49
|
const doc = (node && node.ownerDocument) || document;
|
|
12
50
|
return doc.defaultView || window;
|
|
13
51
|
};
|
|
14
52
|
|
|
15
|
-
function getStyleValue(
|
|
16
|
-
return parseInt(
|
|
53
|
+
function getStyleValue(value: string) {
|
|
54
|
+
return parseInt(value, 10) || 0;
|
|
17
55
|
}
|
|
18
56
|
|
|
19
57
|
interface TextareaAutosizeProps
|
|
@@ -30,34 +68,46 @@ interface TextareaAutosizeProps
|
|
|
30
68
|
* @default 1
|
|
31
69
|
*/
|
|
32
70
|
minRows?: number;
|
|
71
|
+
/**
|
|
72
|
+
* If true, textarea will never get `overflow:hidden`
|
|
73
|
+
*/
|
|
74
|
+
autoScrollbar?: boolean;
|
|
33
75
|
}
|
|
34
76
|
|
|
35
77
|
const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
|
36
78
|
(
|
|
37
|
-
{
|
|
79
|
+
{
|
|
80
|
+
className,
|
|
81
|
+
onChange,
|
|
82
|
+
maxRows,
|
|
83
|
+
minRows = 1,
|
|
84
|
+
autoScrollbar,
|
|
85
|
+
style,
|
|
86
|
+
value,
|
|
87
|
+
...other
|
|
88
|
+
}: TextareaAutosizeProps,
|
|
38
89
|
ref
|
|
39
90
|
) => {
|
|
40
91
|
const { current: isControlled } = useRef(value != null);
|
|
41
|
-
const inputRef = useRef<HTMLTextAreaElement
|
|
92
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
42
93
|
const handleRef = useMemo(() => mergeRefs([inputRef, ref]), [ref]);
|
|
43
|
-
const shadowRef = useRef<HTMLTextAreaElement
|
|
94
|
+
const shadowRef = useRef<HTMLTextAreaElement>(null);
|
|
44
95
|
const renders = useRef(0);
|
|
45
|
-
const [state, setState] = useState<
|
|
96
|
+
const [state, setState] = useState<State>({ outerHeightStyle: 0 });
|
|
46
97
|
|
|
47
98
|
const getUpdatedState = React.useCallback(() => {
|
|
48
|
-
|
|
49
|
-
const input = inputRef.current;
|
|
99
|
+
const input = inputRef.current!;
|
|
50
100
|
const containerWindow = ownerWindow(input);
|
|
51
101
|
const computedStyle = containerWindow.getComputedStyle(input);
|
|
52
102
|
|
|
53
103
|
// If input's width is shrunk and it's not visible, don't sync height.
|
|
54
104
|
if (computedStyle.width === "0px") {
|
|
55
|
-
return;
|
|
105
|
+
return { outerHeightStyle: 0 };
|
|
56
106
|
}
|
|
57
107
|
|
|
58
|
-
const inputShallow = shadowRef.current
|
|
108
|
+
const inputShallow = shadowRef.current!;
|
|
59
109
|
inputShallow.style.width = computedStyle.width;
|
|
60
|
-
inputShallow.value = input.value || other
|
|
110
|
+
inputShallow.value = input.value || other.placeholder || "x";
|
|
61
111
|
if (inputShallow.value.slice(-1) === "\n") {
|
|
62
112
|
// Certain fonts which overflow the line height will cause the textarea
|
|
63
113
|
// to report a different scrollHeight depending on whether the last line
|
|
@@ -65,13 +115,13 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
|
|
65
115
|
inputShallow.value += " ";
|
|
66
116
|
}
|
|
67
117
|
|
|
68
|
-
const boxSizing = computedStyle
|
|
118
|
+
const boxSizing = computedStyle.boxSizing;
|
|
69
119
|
const padding =
|
|
70
|
-
getStyleValue(computedStyle
|
|
71
|
-
getStyleValue(computedStyle
|
|
120
|
+
getStyleValue(computedStyle.paddingBottom) +
|
|
121
|
+
getStyleValue(computedStyle.paddingTop);
|
|
72
122
|
const border =
|
|
73
|
-
getStyleValue(computedStyle
|
|
74
|
-
getStyleValue(computedStyle
|
|
123
|
+
getStyleValue(computedStyle.borderBottomWidth) +
|
|
124
|
+
getStyleValue(computedStyle.borderTopWidth);
|
|
75
125
|
|
|
76
126
|
// The height of the inner content
|
|
77
127
|
const innerHeight = inputShallow.scrollHeight - padding;
|
|
@@ -97,7 +147,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
|
|
97
147
|
const overflow = Math.abs(outerHeight - innerHeight) <= 1;
|
|
98
148
|
|
|
99
149
|
return { outerHeightStyle, overflow };
|
|
100
|
-
}, [maxRows, minRows, other
|
|
150
|
+
}, [maxRows, minRows, other.placeholder]);
|
|
101
151
|
|
|
102
152
|
const syncHeight = React.useCallback(() => {
|
|
103
153
|
const newState = getUpdatedState();
|
|
@@ -106,85 +156,52 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
|
|
106
156
|
return;
|
|
107
157
|
}
|
|
108
158
|
|
|
109
|
-
setState((prevState) =>
|
|
110
|
-
return updateState(prevState, newState);
|
|
111
|
-
});
|
|
159
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
112
160
|
}, [getUpdatedState]);
|
|
113
161
|
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
// This prevents infinite rendering loop.
|
|
118
|
-
if (
|
|
119
|
-
renders.current < 20 &&
|
|
120
|
-
((outerHeightStyle > 0 &&
|
|
121
|
-
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
122
|
-
prevState.overflow !== overflow)
|
|
123
|
-
) {
|
|
124
|
-
renders.current += 1;
|
|
125
|
-
return {
|
|
126
|
-
overflow,
|
|
127
|
-
outerHeightStyle,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
if (process.env.NODE_ENV !== "production") {
|
|
131
|
-
if (renders.current === 20) {
|
|
132
|
-
console.error(
|
|
133
|
-
[
|
|
134
|
-
"Textarea: Too many re-renders. The layout is unstable.",
|
|
135
|
-
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
136
|
-
].join("\n")
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return prevState;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const withFlushSync = () => {
|
|
144
|
-
const newState = getUpdatedState();
|
|
162
|
+
useClientLayoutEffect(() => {
|
|
163
|
+
const syncHeightWithFlushSync = () => {
|
|
164
|
+
const newState = getUpdatedState();
|
|
145
165
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
if (isEmpty(newState)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
149
169
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
170
|
+
// In React 18, state updates in a ResizeObserver's callback are happening after
|
|
171
|
+
// the paint, this leads to an infinite rendering.
|
|
172
|
+
//
|
|
173
|
+
// Using flushSync ensures that the states is updated before the next pain.
|
|
174
|
+
// Related issue - https://github.com/facebook/react/issues/24331
|
|
175
|
+
ReactDOM.flushSync(() => {
|
|
176
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
156
177
|
});
|
|
157
|
-
}
|
|
158
|
-
};
|
|
178
|
+
};
|
|
159
179
|
|
|
160
|
-
|
|
161
|
-
const handleResize = debounce(() => {
|
|
180
|
+
const handleResize = () => {
|
|
162
181
|
renders.current = 0;
|
|
182
|
+
syncHeightWithFlushSync();
|
|
183
|
+
};
|
|
163
184
|
|
|
164
|
-
|
|
165
|
-
withFlushSync();
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
let resizeObserver: ResizeObserver;
|
|
169
|
-
|
|
185
|
+
const debounceHandleResize = debounce(handleResize);
|
|
170
186
|
const input = inputRef.current!;
|
|
171
187
|
const containerWindow = ownerWindow(input);
|
|
172
188
|
|
|
173
|
-
containerWindow.addEventListener("resize",
|
|
189
|
+
containerWindow.addEventListener("resize", debounceHandleResize);
|
|
174
190
|
|
|
191
|
+
let resizeObserver: ResizeObserver;
|
|
175
192
|
if (typeof ResizeObserver !== "undefined") {
|
|
176
193
|
resizeObserver = new ResizeObserver(handleResize);
|
|
177
194
|
resizeObserver.observe(input);
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
return () => {
|
|
181
|
-
|
|
182
|
-
containerWindow.removeEventListener("resize",
|
|
198
|
+
debounceHandleResize.clear();
|
|
199
|
+
containerWindow.removeEventListener("resize", debounceHandleResize);
|
|
183
200
|
if (resizeObserver) {
|
|
184
201
|
resizeObserver.disconnect();
|
|
185
202
|
}
|
|
186
203
|
};
|
|
187
|
-
});
|
|
204
|
+
}, [getUpdatedState]);
|
|
188
205
|
|
|
189
206
|
useClientLayoutEffect(() => {
|
|
190
207
|
syncHeight();
|
|
@@ -194,7 +211,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
|
|
194
211
|
renders.current = 0;
|
|
195
212
|
}, [value]);
|
|
196
213
|
|
|
197
|
-
const handleChange = (event) => {
|
|
214
|
+
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
198
215
|
renders.current = 0;
|
|
199
216
|
|
|
200
217
|
if (!isControlled) {
|
|
@@ -218,7 +235,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
|
|
218
235
|
height: state.outerHeightStyle,
|
|
219
236
|
// Need a large enough difference to allow scrolling.
|
|
220
237
|
// This prevents infinite rendering loop.
|
|
221
|
-
|
|
238
|
+
overflow: state.overflow && !autoScrollbar ? "hidden" : undefined,
|
|
222
239
|
...style,
|
|
223
240
|
}}
|
|
224
241
|
{...other}
|
|
@@ -250,12 +267,12 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
|
|
250
267
|
}
|
|
251
268
|
);
|
|
252
269
|
|
|
253
|
-
function isEmpty(obj:
|
|
270
|
+
function isEmpty(obj: State) {
|
|
254
271
|
return (
|
|
255
272
|
obj === undefined ||
|
|
256
273
|
obj === null ||
|
|
257
274
|
Object.keys(obj).length === 0 ||
|
|
258
|
-
(obj
|
|
275
|
+
(obj.outerHeightStyle === 0 && !obj.overflow)
|
|
259
276
|
);
|
|
260
277
|
}
|
|
261
278
|
|