@navikt/ds-react 5.10.4 → 5.11.1
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/Switch.js +3 -3
- package/cjs/form/Textarea.js +4 -3
- package/cjs/form/TextareaCounter.js +45 -6
- package/cjs/util/TextareaAutoSize.js +67 -69
- package/esm/form/Switch.js +3 -3
- package/esm/form/Switch.js.map +1 -1
- 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/Switch.tsx +7 -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
|
@@ -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
|
|